blob: 5f26595e3ebac3f79cd99d27233fb8c74c816aca [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
const double _kTableRowVerticalMargin = 5.0;
const double _kMargin = 16.0;
const double _kBoxRadius = 4.0;
final TextStyle _kInfoStyle = new TextStyle(
color: Colors.grey[600],
fontStyle: FontStyle.italic,
);
const TextStyle _kCodeStyle = const TextStyle(
fontFamily: 'monospace',
fontWeight: FontWeight.bold,
);
/// A function type to be used as setState() function.
typedef SetStateFunc = void Function(VoidCallback fn);
/// A class containing all the generated state values and widget builders.
///
/// The concrete implementations of this class should be provided by the
/// `gen_widget_specs` tool.
abstract class GeneratedState {
/// The `setState` function provided by the [WidgetExplorerWrapperState].
final SetStateFunc setState;
/// Creates a new instance of [GeneratedState] object with the given
/// [setState] function.
GeneratedState(this.setState);
/// Initialize all the parameter values.
void initState(Map<String, dynamic> config);
/// Builds the target widget with the current values.
Widget buildWidget(
BuildContext context,
Key key,
double width,
double height,
);
/// Builds the [TableRow]s, each of which represents a parameter description
/// and its controller widgets.
List<TableRow> buildParameterTableRows(BuildContext context);
}
/// Builder function for the [GeneratedState].
typedef GeneratedStateBuilder = GeneratedState Function(SetStateFunc setState);
/// A widget that wraps the target widget and its size control panel.
class WidgetExplorerWrapper extends StatefulWidget {
/// Configuration map.
final Map<String, dynamic> config;
/// Current width value.
final double width;
/// Current height value.
final double height;
/// The state builder provided for the target widget.
final GeneratedStateBuilder stateBuilder;
/// Creates a new instance of [WidgetExplorerWrapper].
const WidgetExplorerWrapper({
@required this.config,
@required this.width,
@required this.height,
@required this.stateBuilder,
Key key,
}) : super(key: key);
@override
WidgetExplorerWrapperState createState() => new WidgetExplorerWrapperState();
}
/// The [State] class for the [WidgetExplorerWrapper].
///
/// The most important states are in fact stored in the [genState] field.
class WidgetExplorerWrapperState extends State<WidgetExplorerWrapper> {
/// A [UniqueKey] to be used for the target widget.
Key uniqueKey = new UniqueKey();
/// An internal, generated state object that manages widget-specific states.
GeneratedState genState;
@override
void initState() {
super.initState();
genState = widget.stateBuilder((VoidCallback fn) {
setState(() {
fn?.call();
_updateKey();
});
})
..initState(widget.config);
}
@override
Widget build(BuildContext context) {
Widget targetWidget;
try {
targetWidget = genState.buildWidget(
context,
uniqueKey,
widget.width,
widget.height,
);
} on Exception catch (e) {
targetWidget = new Text('Failed to build the widget.\n'
'See the error message below:\n\n'
'$e');
}
return new ListBody(
children: <Widget>[
new Container(
decoration: new BoxDecoration(
border: new Border.all(color: Colors.grey[500]),
borderRadius:
const BorderRadius.all(const Radius.circular(_kBoxRadius)),
),
margin: const EdgeInsets.all(_kMargin),
child: new Container(
child: new Container(
margin: const EdgeInsets.all(_kMargin),
child: new ListBody(
children: <Widget>[
const Text(
'Parameters',
style: const TextStyle(fontWeight: FontWeight.bold),
),
new Table(
children: genState.buildParameterTableRows(context),
columnWidths: const <int, TableColumnWidth>{
0: const IntrinsicColumnWidth(),
1: const FixedColumnWidth(_kMargin),
2: const IntrinsicColumnWidth(),
3: const FixedColumnWidth(_kMargin),
4: const FlexColumnWidth(1.0),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
),
],
),
),
),
),
new Container(
decoration: new BoxDecoration(
border: new Border.all(color: Colors.grey[500]),
borderRadius:
const BorderRadius.all(const Radius.circular(_kBoxRadius)),
),
margin: const EdgeInsets.all(_kMargin),
child: new Container(
margin: const EdgeInsets.all(_kMargin),
child: new Row(
children: <Widget>[
new Container(
width: widget.width,
height: widget.height,
child: targetWidget,
),
new Expanded(child: new Container()),
],
),
),
),
],
);
}
void _updateKey() {
uniqueKey = new UniqueKey();
}
}
/// Wrapper widget which gives some top margin to a given child.
class _TopMargined extends StatelessWidget {
const _TopMargined({
@required this.child,
Key key,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return new Container(
margin: const EdgeInsets.symmetric(vertical: _kTableRowVerticalMargin),
child: child,
);
}
}
/// Builds a [TableRow] representing a parameter for a widget.
TableRow buildTableRow(BuildContext context, List<Widget> children) {
assert(context != null);
assert(children.length == 3);
return new TableRow(
children: <Widget>[
new _TopMargined(
child: DefaultTextStyle.merge(
style: _kCodeStyle,
child: children[0],
),
),
new Container(), // Empty column
new _TopMargined(
child: DefaultTextStyle.merge(
style: _kCodeStyle,
child: children[1],
),
),
new Container(), // Empty column
new _TopMargined(
child: children[2],
)
],
);
}
/// Regenerate button.
class RegenerateButton extends StatelessWidget {
/// A callback function to be called when this button is pressed.
final VoidCallback onPressed;
/// A code snippet to display along with the button.
final String codeToDisplay;
/// Creates a new instance of [RegenerateButton].
const RegenerateButton({
@required this.onPressed,
Key key,
this.codeToDisplay,
}) : super(key: key);
@override
Widget build(BuildContext context) {
String text =
codeToDisplay != null ? 'Regenerate ($codeToDisplay)' : 'Regenerate';
return new Row(
children: <Widget>[
new RaisedButton(
onPressed: onPressed,
child: new Text(text),
),
new Expanded(child: new Container()),
],
);
}
}
/// A text widget for information displayed in the parameter tuning panel.
class InfoText extends StatelessWidget {
/// Text to display.
final String text;
/// Creates a new instance of [InfoText].
const InfoText(this.text, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) => new Text(
text,
key: key,
style: _kInfoStyle,
);
}
/// A widget indicating whether a config value has been successfully retrieved.
class ConfigKeyText extends StatelessWidget {
/// The key name of the config value specified in the `@ConfigKey` annotation.
final String configKey;
/// The value associated with the key.
final String configValue;
/// Creates a new instance of [ConfigKeyText].
const ConfigKeyText({
@required this.configKey,
@required this.configValue,
Key key,
}) : assert(configKey != null),
super(key: key);
@override
Widget build(BuildContext context) {
if (configValue == null) {
return new InfoText(
"WARNING: Could not find the '$configKey' value "
'from the config.json file.',
);
}
return new InfoText(
"'$configKey' value retrieved from the config.json file.",
);
}
}
/// A helper widget for text input, which can take an initial value string.
class TextFieldWithInitialValue extends StatefulWidget {
/// Initial text value to be used (can be null).
final String initialValue;
/// Keyboard type.
final TextInputType keyboardType;
/// Callback for when the text value changes.
final ValueChanged<String> onChanged;
/// Callback for when the current text is submitted.
final ValueChanged<String> onSubmitted;
/// Creates a new instance of [TextFieldWithInitialValue].
const TextFieldWithInitialValue({
// ignore: avoid_unused_constructor_parameters
Key key,
this.initialValue,
this.keyboardType,
this.onChanged,
this.onSubmitted,
});
@override
_TextFieldWithInitialValueState createState() =>
new _TextFieldWithInitialValueState();
}
class _TextFieldWithInitialValueState extends State<TextFieldWithInitialValue> {
TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = new TextEditingController(text: widget.initialValue ?? '');
}
@override
Widget build(BuildContext context) {
return new TextField(
controller: _controller,
decoration: const InputDecoration(isDense: true),
keyboardType: widget.keyboardType,
onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted,
);
}
}