mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-11-28 16:50:24 +08:00
* fix: prevent custom scale dialog from closing when interacting with slider Wrapped MobileCustomScaleControls in GestureDetector with opaque behavior to prevent touch events from propagating to parent dialog's clickMaskDismiss handler. The slider now works correctly without closing the dialog. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Update flutter/lib/mobile/widgets/custom_scale_widget.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/mobile/widgets/custom_scale_widget.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/mobile/widgets/custom_scale_widget.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/mobile/widgets/custom_scale_widget.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revert "fix: mobile remove "Scale custom" (#13323)" This reverts commit265d08fc3b. * chore: keep remote_toolbar.dart cleanup (remove dead code) The dead code removed in265d08fc3hasn't been used since Aug 2023. Only reverting toolbar.dart is needed for the mobile Scale custom fix. * Update flutter/lib/mobile/pages/remote_page.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: Implement CustomScaleControlsMixin for shared scaling logic across mobile and desktop widgets - Introduced a new mixin `CustomScaleControlsMixin` to encapsulate custom scale control logic, allowing for code reuse in both mobile and desktop widgets. - Refactored `_CustomScaleMenuControlsState` and `_MobileCustomScaleControlsState` to utilize the new mixin, simplifying the scaling logic and reducing code duplication. - Updated slider handling and state management to leverage the mixin's methods for improved maintainability. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Update flutter/lib/desktop/widgets/remote_toolbar.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/mobile/widgets/custom_scale_widget.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/mobile/pages/remote_page.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: changed from mixin to abstract class Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Revert "Update flutter/lib/mobile/pages/remote_page.dart" This reverts commit7c35897408. * refactor: remove unnecessary tap event handling in custom scale controls - Removed the `onTap` handler from the Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor: simplify MobileCustomScaleControls usage in remote_page.dart - Removed unnecessary GestureDetector wrapper around MobileCustomScaleControls for cleaner code. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> --------- Signed-off-by: Alessandro De Blasis <alex@deblasis.net> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
156 lines
4.8 KiB
Dart
156 lines
4.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:debounce_throttle/debounce_throttle.dart';
|
|
import 'package:flutter_hbb/consts.dart';
|
|
import 'package:flutter_hbb/models/model.dart';
|
|
import 'package:flutter_hbb/models/platform_model.dart';
|
|
import 'package:flutter_hbb/utils/scale.dart';
|
|
import 'package:flutter_hbb/common.dart';
|
|
|
|
/// Base class providing shared custom scale control logic for both mobile and desktop widgets.
|
|
/// Implementations must provide [ffi] and [onScaleChanged] getters.
|
|
abstract class CustomScaleControls<T extends StatefulWidget> extends State<T> {
|
|
/// FFI instance for session interaction
|
|
FFI get ffi;
|
|
|
|
/// Callback invoked when scale value changes
|
|
ValueChanged<int>? get onScaleChanged;
|
|
|
|
late int _scaleValue;
|
|
late final Debouncer<int> _debouncerScale;
|
|
// Normalized slider position in [0, 1]. We map it nonlinearly to percent.
|
|
double _scalePos = 0.0;
|
|
|
|
int get scaleValue => _scaleValue;
|
|
double get scalePos => _scalePos;
|
|
|
|
int mapPosToPercent(double p) => _mapPosToPercent(p);
|
|
|
|
static const int minPercent = kScaleCustomMinPercent;
|
|
static const int pivotPercent = kScaleCustomPivotPercent; // 100% should be at 1/3 of track
|
|
static const int maxPercent = kScaleCustomMaxPercent;
|
|
static const double pivotPos = kScaleCustomPivotPos; // first 1/3 → up to 100%
|
|
static const double detentEpsilon = kScaleCustomDetentEpsilon; // snap range around pivot (~0.6%)
|
|
|
|
// Clamp helper for local use
|
|
int _clampScale(int v) => clampCustomScalePercent(v);
|
|
|
|
// Map normalized position [0,1] → percent [5,1000] with 100 at 1/3 width.
|
|
int _mapPosToPercent(double p) {
|
|
if (p <= 0.0) return minPercent;
|
|
if (p >= 1.0) return maxPercent;
|
|
if (p <= pivotPos) {
|
|
final q = p / pivotPos; // 0..1
|
|
final v = minPercent + q * (pivotPercent - minPercent);
|
|
return _clampScale(v.round());
|
|
} else {
|
|
final q = (p - pivotPos) / (1.0 - pivotPos); // 0..1
|
|
final v = pivotPercent + q * (maxPercent - pivotPercent);
|
|
return _clampScale(v.round());
|
|
}
|
|
}
|
|
|
|
// Map percent [5,1000] → normalized position [0,1]
|
|
double _mapPercentToPos(int percent) {
|
|
final p = _clampScale(percent);
|
|
if (p <= pivotPercent) {
|
|
final q = (p - minPercent) / (pivotPercent - minPercent);
|
|
return q * pivotPos;
|
|
} else {
|
|
final q = (p - pivotPercent) / (maxPercent - pivotPercent);
|
|
return pivotPos + q * (1.0 - pivotPos);
|
|
}
|
|
}
|
|
|
|
// Snap normalized position to the pivot when close to it
|
|
double _snapNormalizedPos(double p) {
|
|
if ((p - pivotPos).abs() <= detentEpsilon) return pivotPos;
|
|
if (p < 0.0) return 0.0;
|
|
if (p > 1.0) return 1.0;
|
|
return p;
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_scaleValue = 100;
|
|
_debouncerScale = Debouncer<int>(
|
|
kDebounceCustomScaleDuration,
|
|
onChanged: (v) async {
|
|
await _applyScale(v);
|
|
},
|
|
initialValue: _scaleValue,
|
|
);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
try {
|
|
final v = await getSessionCustomScalePercent(ffi.sessionId);
|
|
if (mounted) {
|
|
setState(() {
|
|
_scaleValue = v;
|
|
_scalePos = _mapPercentToPos(v);
|
|
});
|
|
}
|
|
} catch (e, st) {
|
|
debugPrint('[CustomScale] Failed to get initial value: $e');
|
|
debugPrintStack(stackTrace: st);
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _applyScale(int v) async {
|
|
v = clampCustomScalePercent(v);
|
|
setState(() {
|
|
_scaleValue = v;
|
|
});
|
|
try {
|
|
await bind.sessionSetFlutterOption(
|
|
sessionId: ffi.sessionId,
|
|
k: kCustomScalePercentKey,
|
|
v: v.toString());
|
|
final curStyle = await bind.sessionGetViewStyle(sessionId: ffi.sessionId);
|
|
if (curStyle != kRemoteViewStyleCustom) {
|
|
await bind.sessionSetViewStyle(
|
|
sessionId: ffi.sessionId, value: kRemoteViewStyleCustom);
|
|
}
|
|
await ffi.canvasModel.updateViewStyle();
|
|
if (isMobile) {
|
|
HapticFeedback.selectionClick();
|
|
}
|
|
onScaleChanged?.call(v);
|
|
} catch (e, st) {
|
|
debugPrint('[CustomScale] Apply failed: $e');
|
|
debugPrintStack(stackTrace: st);
|
|
}
|
|
}
|
|
|
|
void nudgeScale(int delta) {
|
|
final next = _clampScale(_scaleValue + delta);
|
|
setState(() {
|
|
_scaleValue = next;
|
|
_scalePos = _mapPercentToPos(next);
|
|
});
|
|
onScaleChanged?.call(next);
|
|
_debouncerScale.value = next;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_debouncerScale.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void onSliderChanged(double v) {
|
|
final snapped = _snapNormalizedPos(v);
|
|
final next = _mapPosToPercent(snapped);
|
|
if (next != _scaleValue || snapped != _scalePos) {
|
|
setState(() {
|
|
_scalePos = snapped;
|
|
_scaleValue = next;
|
|
});
|
|
onScaleChanged?.call(next);
|
|
_debouncerScale.value = next;
|
|
}
|
|
}
|
|
}
|