feat(canvas): canvas margin options

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2026-05-05 23:35:53 +08:00
parent a5a9c26e17
commit 4f301fe427
3 changed files with 100 additions and 83 deletions

View file

@ -13,7 +13,6 @@ import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/printer_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
@ -1813,21 +1812,25 @@ class _DisplayState extends State<_Display> {
}
Widget remoteCanvasMargin(BuildContext context) {
final canvasModel = Provider.of<CanvasModel>(context, listen: false);
onChanged(double value) async {
await canvasModel.setRemoteCanvasMargin(value);
final normalizedValue = value.clamp(0, 400).round();
await bind.mainSetUserDefaultOption(
key: kOptionRemoteCanvasMargin, value: normalizedValue.toString());
setState(() {});
}
final currentValue = canvasModel.remoteCanvasMargin;
final currentValue = (double.tryParse(bind.mainGetUserDefaultOption(
key: kOptionRemoteCanvasMargin)) ??
0)
.clamp(0, 400)
.toDouble();
return _Card(title: 'Remote canvas margin', children: [
return _Card(title: 'canvas_margin', children: [
EdgeThicknessControl(
value: currentValue,
min: 0,
max: 400,
onChanged:
isOptionFixed(kOptionRemoteCanvasMargin) ? null : onChanged,
onChanged: onChanged,
),
]);
}

View file

@ -1104,32 +1104,24 @@ class _DisplayMenuState extends State<_DisplayMenu> {
return futureBuilder(future: () async {
final viewStyle =
await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? '';
final visible = viewStyle == kRemoteViewStyleOriginal ||
final scrollVisible = viewStyle == kRemoteViewStyleOriginal ||
viewStyle == kRemoteViewStyleCustom;
final scrollStyle =
await bind.sessionGetScrollStyle(sessionId: ffi.sessionId) ?? '';
final edgeScrollEdgeThickness = await bind
.sessionGetEdgeScrollEdgeThickness(sessionId: ffi.sessionId);
final remoteCanvasMargin =
(double.tryParse(bind.mainGetUserDefaultOption(
key: kOptionRemoteCanvasMargin)) ??
0)
.clamp(0, 400)
.toDouble();
await widget.ffi.canvasModel.initializeRemoteCanvasMargin();
return {
'visible': visible,
'scrollVisible': scrollVisible,
'scrollStyle': scrollStyle,
'edgeScrollEdgeThickness': edgeScrollEdgeThickness,
'remoteCanvasMargin': remoteCanvasMargin,
'remoteCanvasMargin': widget.ffi.canvasModel.remoteCanvasMargin,
};
}(), hasData: (data) {
final visible = data['visible'] as bool;
if (!visible) return Offstage();
final scrollVisible = data['scrollVisible'] as bool;
final groupValue = data['scrollStyle'] as String;
final edgeScrollEdgeThickness = data['edgeScrollEdgeThickness'] as int;
final remoteCanvasMargin = data['remoteCanvasMargin'] as double;
final isRemoteCanvasMarginFixed =
isOptionFixed(kOptionRemoteCanvasMargin);
onChangeScrollStyle(String? value) async {
if (value == null) return;
@ -1149,73 +1141,73 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
onChangeRemoteCanvasMargin(double? value) async {
if (value == null || isRemoteCanvasMarginFixed) return;
if (value == null) return;
await widget.ffi.canvasModel.setRemoteCanvasMargin(value);
state.setState(() {});
}
return Obx(() => Column(children: [
RdoMenuButton<String>(
child: Text(translate('ScrollAuto')),
value: kRemoteScrollStyleAuto,
groupValue: groupValue,
onChanged: widget.ffi.canvasModel.imageOverflow.value
? (value) => onChangeScrollStyle(value)
: null,
closeOnActivate: groupValue != kRemoteScrollStyleEdge,
ffi: widget.ffi,
),
RdoMenuButton<String>(
child: Text(translate('Scrollbar')),
value: kRemoteScrollStyleBar,
groupValue: groupValue,
onChanged: widget.ffi.canvasModel.imageOverflow.value
? (value) => onChangeScrollStyle(value)
: null,
closeOnActivate: groupValue != kRemoteScrollStyleEdge,
ffi: widget.ffi,
),
if (!isWeb) ...[
RdoMenuButton<String>(
child: Text(translate('ScrollEdge')),
value: kRemoteScrollStyleEdge,
return Column(children: [
if (scrollVisible) ...[
Obx(() => RdoMenuButton<String>(
child: Text(translate('ScrollAuto')),
value: kRemoteScrollStyleAuto,
groupValue: groupValue,
closeOnActivate: false,
onChanged: widget.ffi.canvasModel.imageOverflow.value
? (value) => onChangeScrollStyle(value)
: null,
closeOnActivate: groupValue != kRemoteScrollStyleEdge,
ffi: widget.ffi,
)),
Obx(() => RdoMenuButton<String>(
child: Text(translate('Scrollbar')),
value: kRemoteScrollStyleBar,
groupValue: groupValue,
onChanged: widget.ffi.canvasModel.imageOverflow.value
? (value) => onChangeScrollStyle(value)
: null,
closeOnActivate: groupValue != kRemoteScrollStyleEdge,
ffi: widget.ffi,
)),
if (!isWeb) ...[
Obx(() => RdoMenuButton<String>(
child: Text(translate('ScrollEdge')),
value: kRemoteScrollStyleEdge,
groupValue: groupValue,
closeOnActivate: false,
onChanged: widget.ffi.canvasModel.imageOverflow.value
? (value) => onChangeScrollStyle(value)
: null,
ffi: widget.ffi,
)),
Offstage(
offstage: groupValue != kRemoteScrollStyleEdge,
child: EdgeThicknessControl(
value: edgeScrollEdgeThickness.toDouble(),
onChanged: onChangeEdgeScrollEdgeThickness,
colorScheme: colorScheme,
)),
],
],
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
Expanded(child: Text(translate('canvas_margin'))),
SizedBox(
width: 160,
child: EdgeThicknessControl(
value: remoteCanvasMargin,
min: 0,
max: 400,
onChanged: onChangeRemoteCanvasMargin,
colorScheme: colorScheme,
),
),
Offstage(
offstage: groupValue != kRemoteScrollStyleEdge,
child: EdgeThicknessControl(
value: edgeScrollEdgeThickness.toDouble(),
onChanged: onChangeEdgeScrollEdgeThickness,
colorScheme: colorScheme,
)),
],
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
Expanded(child: Text(translate('canvas_margin'))),
SizedBox(
width: 160,
child: EdgeThicknessControl(
value: remoteCanvasMargin,
min: 0,
max: 400,
onChanged: isRemoteCanvasMarginFixed
? null
: onChangeRemoteCanvasMargin,
colorScheme: colorScheme,
),
),
],
),
),
Divider(),
]));
),
),
Divider(),
]);
});
}

View file

@ -2154,6 +2154,8 @@ class CanvasModel with ChangeNotifier {
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
// edge scroll mode: trigger scrolling when the cursor is close to the edge of the view
int _edgeScrollEdgeThickness = 100;
double _remoteCanvasMargin = 0;
bool _remoteCanvasMarginInitialized = false;
// tracks whether edge scroll should be active, prevents spurious
// scrolling when the cursor enters the view from outside
EdgeScrollState _edgeScrollState = EdgeScrollState.inactive;
@ -2197,19 +2199,35 @@ class CanvasModel with ChangeNotifier {
if (!(isDesktop || isWebDesktop)) {
return 0;
}
final value =
double.tryParse(bind.mainGetUserDefaultOption(key: kOptionRemoteCanvasMargin)) ??
0;
return min(400, max(0, value));
return _remoteCanvasMargin;
}
Future<void> setRemoteCanvasMargin(double value) async {
final normalizedValue = value.clamp(0, 400).round();
await bind.mainSetUserDefaultOption(
key: kOptionRemoteCanvasMargin, value: normalizedValue.toString());
await bind.sessionSetFlutterOption(
sessionId: sessionId,
k: kOptionRemoteCanvasMargin,
v: normalizedValue.toString());
_remoteCanvasMargin = normalizedValue.toDouble();
_remoteCanvasMarginInitialized = true;
await updateViewStyle();
}
Future<void> initializeRemoteCanvasMargin() async {
if (_remoteCanvasMarginInitialized || !(isDesktop || isWebDesktop)) {
return;
}
final sessionValue = await bind.sessionGetFlutterOption(
sessionId: sessionId, k: kOptionRemoteCanvasMargin);
final defaultValue =
bind.mainGetUserDefaultOption(key: kOptionRemoteCanvasMargin);
final value =
sessionValue?.isNotEmpty == true ? sessionValue : defaultValue;
_remoteCanvasMargin =
(double.tryParse(value ?? '') ?? 0).clamp(0, 400).toDouble();
_remoteCanvasMarginInitialized = true;
}
Rect? get paddedRect {
final rect = realRect;
if (rect == null) {
@ -2328,6 +2346,7 @@ class CanvasModel with ChangeNotifier {
return;
}
await initializeRemoteCanvasMargin();
updateSize();
final displayWidth = getDisplayWidth();
final displayHeight = getDisplayHeight();
@ -2706,6 +2725,8 @@ class CanvasModel with ChangeNotifier {
_y = 0;
_scale = 1.0;
_lastViewStyle = ViewStyle.defaultViewStyle();
_remoteCanvasMargin = 0;
_remoteCanvasMarginInitialized = false;
_timerMobileFocusCanvasCursor?.cancel();
_timerMobileRestoreCanvasOffset?.cancel();
_offsetBeforeMobileSoftKeyboard = null;
@ -3230,7 +3251,8 @@ class CursorModel with ChangeNotifier {
dx = min(dx, maxCursorCanMove);
}
} else if (dx < 0) {
final maxCanvasCanMove = (displayRect?.left ?? 0) - r.left.roundToDouble();
final maxCanvasCanMove =
(displayRect?.left ?? 0) - r.left.roundToDouble();
tryMoveCanvasX = _x + dx < cx && maxCanvasCanMove < 0;
if (tryMoveCanvasX) {
dx = max(dx, maxCanvasCanMove);