Skip to content
19 changes: 15 additions & 4 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import 'tooltip.dart';
import 'transparent_pointer.dart';
import 'vertical_divider.dart';
import 'window_drag_area.dart';
import 'range_slider.dart';

Widget createControl(Control? parent, String id, bool parentDisabled,
{Widget? nextChild}) {
Expand Down Expand Up @@ -439,10 +440,20 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
dispatch: controlView.dispatch);
case "slider":
return SliderControl(
key: key,
parent: parent,
control: controlView.control,
parentDisabled: parentDisabled);
key: key,
parent: parent,
control: controlView.control,
parentDisabled: parentDisabled,
dispatch: controlView.dispatch,
);
case "rangeslider":
return RangeSliderControl(
key: key,
parent: parent,
control: controlView.control,
parentDisabled: parentDisabled,
dispatch: controlView.dispatch,
);
case "radiogroup":
return RadioGroupControl(
key: key,
Expand Down
2 changes: 1 addition & 1 deletion package/lib/src/controls/date_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class _DatePickerControlState extends State<DatePickerControl> {
value?.toIso8601String() ?? currentDate?.toIso8601String() ?? "";
eventName = "dismiss";
} else {
stringValue = dateValue?.toIso8601String() ?? "";
stringValue = dateValue.toIso8601String();
eventName = "change";
}
List<Map<String, String>> props = [
Expand Down
126 changes: 126 additions & 0 deletions package/lib/src/controls/range_slider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import '../actions.dart';
import '../flet_app_services.dart';
import '../models/control.dart';
import '../protocol/update_control_props_payload.dart';
import '../utils/colors.dart';
import '../utils/desktop.dart';
import 'create_control.dart';
import '../utils/buttons.dart';
import '../utils/debouncer.dart';

class RangeSliderControl extends StatefulWidget {
final Control? parent;
final Control control;
final bool parentDisabled;
final dynamic dispatch;

const RangeSliderControl({
Key? key,
this.parent,
required this.control,
required this.parentDisabled,
required this.dispatch,
}) : super(key: key);

@override
State<RangeSliderControl> createState() => _SliderControlState();
}

class _SliderControlState extends State<RangeSliderControl> {
final _debouncer = Debouncer(milliseconds: isDesktop() ? 10 : 100);

@override
void initState() {
super.initState();
}

@override
void dispose() {
_debouncer.dispose();
super.dispose();
}

void onChange(double startValue, double endValue) {
var strStartValue = startValue.toString();
var strEndValue = endValue.toString();

List<Map<String, String>> props = [
{
"i": widget.control.id,
"startvalue": strStartValue,
"endvalue": strEndValue
}
];
widget.dispatch(
UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));

_debouncer.run(() {
final server = FletAppServices.of(context).server;
server.updateControlProps(props: props);
server.sendPageEvent(
eventTarget: widget.control.id, eventName: "change", eventData: '');
});
}

@override
Widget build(BuildContext context) {
debugPrint("RangeSliderControl build: ${widget.control.id}");

double startValue = widget.control.attrDouble("startvalue", 0)!;
double endValue = widget.control.attrDouble("endvalue", 0)!;
String? label = widget.control.attrString("label");
bool disabled = widget.control.isDisabled || widget.parentDisabled;

double min = widget.control.attrDouble("min", 0)!;
double max = widget.control.attrDouble("max", 1)!;

int? divisions = widget.control.attrInt("divisions");
int round = widget.control.attrInt("round", 0)!;

final server = FletAppServices.of(context).server;

debugPrint("SliderControl StoreConnector build: ${widget.control.id}");

var rangeSlider = RangeSlider(
values: RangeValues(startValue, endValue),
labels: RangeLabels(
(label ?? "")
.replaceAll("{value}", startValue.toStringAsFixed(round)),
(label ?? "")
.replaceAll("{value}", endValue.toStringAsFixed(round))),
min: min,
max: max,
divisions: divisions,
activeColor: HexColor.fromString(
Theme.of(context), widget.control.attrString("activeColor", "")!),
inactiveColor: HexColor.fromString(
Theme.of(context), widget.control.attrString("inactiveColor", "")!),
overlayColor: parseMaterialStateColor(
Theme.of(context), widget.control, "overlayColor"),
onChanged: !disabled
? (RangeValues newValues) {
onChange(newValues.start, newValues.end);
}
: null,
onChangeStart: !disabled
? (RangeValues newValues) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change_start",
eventData: '');
}
: null,
onChangeEnd: !disabled
? (RangeValues newValues) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change_end",
eventData: '');
}
: null);

return constrainedControl(
context, rangeSlider, widget.parent, widget.control);
}
}
122 changes: 55 additions & 67 deletions package/lib/src/controls/slider.dart
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';

import '../actions.dart';
import '../flet_app_services.dart';
import '../models/app_state.dart';
import '../models/control.dart';
import '../protocol/update_control_props_payload.dart';
import '../utils/colors.dart';
import '../utils/desktop.dart';
import '../utils/debouncer.dart';
import 'create_control.dart';

class SliderControl extends StatefulWidget {
final Control? parent;
final Control control;
final bool parentDisabled;
final dynamic dispatch;

const SliderControl(
{Key? key,
this.parent,
required this.control,
required this.parentDisabled})
required this.parentDisabled,
required this.dispatch})
: super(key: key);

@override
Expand All @@ -30,7 +28,7 @@ class SliderControl extends StatefulWidget {

class _SliderControlState extends State<SliderControl> {
double _value = 0;
Timer? _debounce;
final _debouncer = Debouncer(milliseconds: isDesktop() ? 10 : 100);
late final FocusNode _focusNode;

@override
Expand All @@ -42,7 +40,7 @@ class _SliderControlState extends State<SliderControl> {

@override
void dispose() {
_debounce?.cancel();
_debouncer.dispose();
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
super.dispose();
Expand All @@ -55,26 +53,24 @@ class _SliderControlState extends State<SliderControl> {
eventData: "");
}

void onChange(double value, Function dispatch) {
void onChange(double value) {
var svalue = value.toString();
debugPrint(svalue);
setState(() {
_value = value;
});

if (_debounce?.isActive ?? false) _debounce!.cancel();
List<Map<String, String>> props = [
{"i": widget.control.id, "value": svalue}
];
dispatch(UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));
widget.dispatch(
UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));

_debounce = Timer(Duration(milliseconds: isDesktop() ? 10 : 100), () {
_debouncer.run(() {
final server = FletAppServices.of(context).server;
server.updateControlProps(props: props);
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change",
eventData: svalue);
eventTarget: widget.control.id, eventName: "change", eventData: '');
});
}

Expand All @@ -93,57 +89,49 @@ class _SliderControlState extends State<SliderControl> {

final server = FletAppServices.of(context).server;

return StoreConnector<AppState, Function>(
distinct: true,
converter: (store) => store.dispatch,
builder: (context, dispatch) {
debugPrint(
"SliderControl StoreConnector build: ${widget.control.id}");

double value = widget.control.attrDouble("value", 0)!;
if (_value != value) {
_value = value;
}

var slider = Slider(
autofocus: autofocus,
focusNode: _focusNode,
value: _value,
min: min,
max: max,
divisions: divisions,
label:
label?.replaceAll("{value}", _value.toStringAsFixed(round)),
activeColor: HexColor.fromString(Theme.of(context),
widget.control.attrString("activeColor", "")!),
inactiveColor: HexColor.fromString(Theme.of(context),
widget.control.attrString("inactiveColor", "")!),
thumbColor: HexColor.fromString(Theme.of(context),
widget.control.attrString("thumbColor", "")!),
onChanged: !disabled
? (double value) {
onChange(value, dispatch);
}
: null,
onChangeStart: !disabled
? (double value) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change_start",
eventData: value.toString());
}
: null,
onChangeEnd: !disabled
? (double value) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change_end",
eventData: value.toString());
}
: null);

return constrainedControl(
context, slider, widget.parent, widget.control);
});
debugPrint("SliderControl StoreConnector build: ${widget.control.id}");

double value = widget.control.attrDouble("value", 0)!;
if (_value != value) {
_value = value;
}

var slider = Slider(
autofocus: autofocus,
focusNode: _focusNode,
value: _value,
min: min,
max: max,
divisions: divisions,
label: label?.replaceAll("{value}", _value.toStringAsFixed(round)),
activeColor: HexColor.fromString(
Theme.of(context), widget.control.attrString("activeColor", "")!),
inactiveColor: HexColor.fromString(
Theme.of(context), widget.control.attrString("inactiveColor", "")!),
thumbColor: HexColor.fromString(
Theme.of(context), widget.control.attrString("thumbColor", "")!),
onChanged: !disabled
? (double value) {
onChange(value);
}
: null,
onChangeStart: !disabled
? (double value) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change_start",
eventData: value.toString());
}
: null,
onChangeEnd: !disabled
? (double value) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change_end",
eventData: value.toString());
}
: null);

return constrainedControl(context, slider, widget.parent, widget.control);
}
}
18 changes: 18 additions & 0 deletions package/lib/src/utils/debouncer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';
import 'dart:async';

class Debouncer {
final int milliseconds;
Timer? _timer;

Debouncer({required this.milliseconds});

void run(VoidCallback action) {
if (_timer?.isActive ?? false) _timer!.cancel();
_timer = Timer(Duration(milliseconds: milliseconds), action);
}

void dispose() {
_timer?.cancel();
}
}
1 change: 1 addition & 0 deletions sdk/python/packages/flet-core/src/flet_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,4 @@
from flet_core.vertical_divider import VerticalDivider
from flet_core.view import View
from flet_core.window_drag_area import WindowDragArea
from flet_core.range_slider import RangeSlider
Loading