mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-06-22 10:02:20 +00:00
Merge ea5cfddccc into 2b40c61d8e
This commit is contained in:
commit
14d2878697
24 changed files with 617 additions and 89 deletions
|
|
@ -85,6 +85,8 @@ const String kWindowEventOpenMonitorSession = "open_monitor_session";
|
|||
const String kOptionViewStyle = "view_style";
|
||||
const String kOptionScrollStyle = "scroll_style";
|
||||
const String kOptionEdgeScrollEdgeThickness = "edge-scroll-edge-thickness";
|
||||
const String kOptionRemoteCanvasMargin = "remote-canvas-margin";
|
||||
const double kMaxRemoteCanvasMargin = 400.0;
|
||||
const String kOptionImageQuality = "image_quality";
|
||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||
const String kOptionTextureRender = "use-texture-render";
|
||||
|
|
|
|||
|
|
@ -1799,6 +1799,7 @@ class _DisplayState extends State<_Display> {
|
|||
return ListView(controller: scrollController, children: [
|
||||
viewStyle(context),
|
||||
scrollStyle(context),
|
||||
remoteCanvasMargin(context),
|
||||
imageQuality(context),
|
||||
codec(context),
|
||||
if (isDesktop) trackpadSpeed(context),
|
||||
|
|
@ -1876,6 +1877,30 @@ class _DisplayState extends State<_Display> {
|
|||
]);
|
||||
}
|
||||
|
||||
Widget remoteCanvasMargin(BuildContext context) {
|
||||
onChanged(double value) async {
|
||||
final normalizedValue = value.clamp(0, kMaxRemoteCanvasMargin).round();
|
||||
await bind.mainSetUserDefaultOption(
|
||||
key: kOptionRemoteCanvasMargin, value: normalizedValue.toString());
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
final currentValue = (double.tryParse(bind.mainGetUserDefaultOption(
|
||||
key: kOptionRemoteCanvasMargin)) ??
|
||||
0)
|
||||
.clamp(0, kMaxRemoteCanvasMargin)
|
||||
.toDouble();
|
||||
|
||||
return _Card(title: 'canvas_margin', children: [
|
||||
EdgeThicknessControl(
|
||||
value: currentValue,
|
||||
min: 0,
|
||||
max: kMaxRemoteCanvasMargin,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget imageQuality(BuildContext context) {
|
||||
onChanged(String value) async {
|
||||
await bind.mainSetUserDefaultOption(
|
||||
|
|
|
|||
|
|
@ -833,7 +833,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||
m.useTextureRender || widget.ffi.ffiModel.pi.forceTextureRender
|
||||
? _BuildPaintTextureRender(
|
||||
c, s, Offset.zero, paintSize, isViewOriginal())
|
||||
: _buildScrollbarNonTextureRender(m, paintSize, s);
|
||||
: _buildScrollbarNonTextureRender(m, c, paintSize, s);
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
c.updateScrollPercent();
|
||||
|
|
@ -871,10 +871,14 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||
}
|
||||
|
||||
Widget _buildScrollbarNonTextureRender(
|
||||
ImageModel m, Size imageSize, double s) {
|
||||
ImageModel m, CanvasModel c, Size imageSize, double s) {
|
||||
return CustomPaint(
|
||||
size: imageSize,
|
||||
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
|
||||
painter: ImagePainter(
|
||||
image: m.image,
|
||||
x: c.displayPaddingX,
|
||||
y: c.displayPaddingY,
|
||||
scale: s),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -891,8 +895,8 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||
size: Size(c.size.width, c.size.height),
|
||||
painter: ImagePainter(
|
||||
image: m.image,
|
||||
x: c.x / sizeScale,
|
||||
y: c.y / sizeScale,
|
||||
x: c.x / sizeScale + c.displayPaddingX,
|
||||
y: c.y / sizeScale + c.displayPaddingY,
|
||||
scale: sizeScale),
|
||||
);
|
||||
}
|
||||
|
|
@ -902,7 +906,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||
final ffiModel = c.parent.target!.ffiModel;
|
||||
final displays = ffiModel.pi.getCurDisplays();
|
||||
final children = <Widget>[];
|
||||
final rect = ffiModel.rect;
|
||||
final rect = c.paddedRect;
|
||||
if (rect == null) {
|
||||
return Container();
|
||||
}
|
||||
|
|
@ -1065,7 +1069,7 @@ class CursorPaint extends StatelessWidget {
|
|||
double cy = c.y;
|
||||
if (c.viewStyle.style == kRemoteViewStyleOriginal &&
|
||||
c.scrollStyle == ScrollStyle.scrollbar) {
|
||||
final rect = c.parent.target!.ffiModel.rect;
|
||||
final rect = c.paddedRect;
|
||||
if (rect == null) {
|
||||
// unreachable!
|
||||
debugPrint('unreachable! The displays rect is null.');
|
||||
|
|
|
|||
|
|
@ -1499,6 +1499,7 @@ class _DisplayMenu extends StatefulWidget {
|
|||
|
||||
class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
final RxInt _customPercent = 100.obs;
|
||||
double? _remoteCanvasMarginPreview;
|
||||
late final ScreenAdjustor _screenAdjustor = ScreenAdjustor(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
|
|
@ -1662,22 +1663,44 @@ 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 edgeScrollEdgeThickness = scrollVisible
|
||||
? await bind.sessionGetEdgeScrollEdgeThickness(
|
||||
sessionId: ffi.sessionId)
|
||||
: null;
|
||||
await widget.ffi.canvasModel.initializeRemoteCanvasMargin();
|
||||
return {
|
||||
'visible': visible,
|
||||
'scrollVisible': scrollVisible,
|
||||
'scrollStyle': scrollStyle,
|
||||
'edgeScrollEdgeThickness': edgeScrollEdgeThickness,
|
||||
'supportsRemoteCanvasMargin':
|
||||
widget.ffi.canvasModel.supportsRemoteCanvasMargin,
|
||||
'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 edgeScrollEdgeThickness =
|
||||
((data['edgeScrollEdgeThickness'] as int?) ??
|
||||
EdgeThicknessControl.kMin.round())
|
||||
.clamp(EdgeThicknessControl.kMin.round(),
|
||||
EdgeThicknessControl.kMax.round())
|
||||
.toInt();
|
||||
final supportsRemoteCanvasMargin =
|
||||
data['supportsRemoteCanvasMargin'] as bool;
|
||||
final savedRemoteCanvasMargin = data['remoteCanvasMargin'] as double;
|
||||
final remoteCanvasMargin =
|
||||
(_remoteCanvasMarginPreview ?? savedRemoteCanvasMargin)
|
||||
.clamp(0, kMaxRemoteCanvasMargin)
|
||||
.toDouble();
|
||||
final hasVisibleControls = scrollVisible || supportsRemoteCanvasMargin;
|
||||
|
||||
if (!hasVisibleControls) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
onChangeScrollStyle(String? value) async {
|
||||
if (value == null) return;
|
||||
|
|
@ -1696,48 +1719,86 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||
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,
|
||||
onChangeRemoteCanvasMargin(double? value) async {
|
||||
if (value == null) return;
|
||||
_remoteCanvasMarginPreview =
|
||||
value.clamp(0, kMaxRemoteCanvasMargin).toDouble();
|
||||
state.setState(() {});
|
||||
}
|
||||
|
||||
onChangeRemoteCanvasMarginEnd(double value) async {
|
||||
_remoteCanvasMarginPreview =
|
||||
value.clamp(0, kMaxRemoteCanvasMargin).toDouble();
|
||||
await widget.ffi.canvasModel.setRemoteCanvasMargin(value);
|
||||
_remoteCanvasMarginPreview = null;
|
||||
state.setState(() {});
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
Offstage(
|
||||
offstage: groupValue != kRemoteScrollStyleEdge,
|
||||
)),
|
||||
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,
|
||||
)),
|
||||
],
|
||||
],
|
||||
if (supportsRemoteCanvasMargin) ...[
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(translate('canvas_margin'))),
|
||||
SizedBox(
|
||||
width: 160,
|
||||
child: EdgeThicknessControl(
|
||||
value: edgeScrollEdgeThickness.toDouble(),
|
||||
onChanged: onChangeEdgeScrollEdgeThickness,
|
||||
value: remoteCanvasMargin,
|
||||
min: 0,
|
||||
max: kMaxRemoteCanvasMargin,
|
||||
onChanged: onChangeRemoteCanvasMargin,
|
||||
onChangeEnd: onChangeRemoteCanvasMarginEnd,
|
||||
colorScheme: colorScheme,
|
||||
)),
|
||||
],
|
||||
Divider(),
|
||||
]));
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
Divider(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -3460,13 +3521,21 @@ Widget _buildPointerTrackWidget(Widget child, FFI? ffi) {
|
|||
class EdgeThicknessControl extends StatelessWidget {
|
||||
final double value;
|
||||
final ValueChanged<double>? onChanged;
|
||||
final ValueChanged<double>? onChangeEnd;
|
||||
final ColorScheme? colorScheme;
|
||||
final double min;
|
||||
final double max;
|
||||
final String unit;
|
||||
|
||||
const EdgeThicknessControl({
|
||||
Key? key,
|
||||
required this.value,
|
||||
this.onChanged,
|
||||
this.onChangeEnd,
|
||||
this.colorScheme,
|
||||
this.min = kMin,
|
||||
this.max = kMax,
|
||||
this.unit = 'px',
|
||||
}) : super(key: key);
|
||||
|
||||
static const double kMin = 20;
|
||||
|
|
@ -3483,25 +3552,25 @@ class EdgeThicknessControl extends StatelessWidget {
|
|||
overlayColor: colorScheme.primary.withOpacity(0.1),
|
||||
showValueIndicator: ShowValueIndicator.never,
|
||||
thumbShape: _RectValueThumbShape(
|
||||
min: EdgeThicknessControl.kMin,
|
||||
max: EdgeThicknessControl.kMax,
|
||||
min: min,
|
||||
max: max,
|
||||
width: 52,
|
||||
height: 24,
|
||||
radius: 4,
|
||||
unit: 'px',
|
||||
unit: unit,
|
||||
),
|
||||
),
|
||||
child: Semantics(
|
||||
value: value.toInt().toString(),
|
||||
child: Slider(
|
||||
value: value,
|
||||
min: EdgeThicknessControl.kMin,
|
||||
max: EdgeThicknessControl.kMax,
|
||||
divisions:
|
||||
(EdgeThicknessControl.kMax - EdgeThicknessControl.kMin).round(),
|
||||
min: min,
|
||||
max: max,
|
||||
divisions: (max - min).round(),
|
||||
semanticFormatterCallback: (double newValue) =>
|
||||
"${newValue.round()}px",
|
||||
"${newValue.round()}$unit",
|
||||
onChanged: onChanged,
|
||||
onChangeEnd: onChangeEnd,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ class CanvasCoords {
|
|||
double scale = 1.0;
|
||||
double scrollX = 0;
|
||||
double scrollY = 0;
|
||||
double displayWidth = 0;
|
||||
double displayHeight = 0;
|
||||
double paddingX = 0;
|
||||
double paddingY = 0;
|
||||
ScrollStyle scrollStyle = ScrollStyle.scrollauto;
|
||||
Size size = Size.zero;
|
||||
|
||||
|
|
@ -46,6 +50,10 @@ class CanvasCoords {
|
|||
'scale': scale,
|
||||
'scrollX': scrollX,
|
||||
'scrollY': scrollY,
|
||||
'displayWidth': displayWidth,
|
||||
'displayHeight': displayHeight,
|
||||
'paddingX': paddingX,
|
||||
'paddingY': paddingY,
|
||||
'scrollStyle': scrollStyle.toJson(),
|
||||
'size': {
|
||||
'w': size.width,
|
||||
|
|
@ -56,14 +64,22 @@ class CanvasCoords {
|
|||
|
||||
static CanvasCoords fromJson(Map<String, dynamic> json) {
|
||||
final model = CanvasCoords();
|
||||
model.x = json['x'];
|
||||
model.y = json['y'];
|
||||
model.scale = json['scale'];
|
||||
model.scrollX = json['scrollX'];
|
||||
model.scrollY = json['scrollY'];
|
||||
model.x = (json['x'] ?? 0).toDouble();
|
||||
model.y = (json['y'] ?? 0).toDouble();
|
||||
model.scale = (json['scale'] ?? 1).toDouble();
|
||||
model.scrollX = (json['scrollX'] ?? 0).toDouble();
|
||||
model.scrollY = (json['scrollY'] ?? 0).toDouble();
|
||||
model.displayWidth = (json['displayWidth'] ?? 0).toDouble();
|
||||
model.displayHeight = (json['displayHeight'] ?? 0).toDouble();
|
||||
model.paddingX = (json['paddingX'] ?? 0).toDouble();
|
||||
model.paddingY = (json['paddingY'] ?? 0).toDouble();
|
||||
model.scrollStyle =
|
||||
ScrollStyle.fromJson(json['scrollStyle'], ScrollStyle.scrollauto);
|
||||
model.size = Size(json['size']['w'], json['size']['h']);
|
||||
final sizeMap = json['size'];
|
||||
model.size = Size(
|
||||
(sizeMap?['w'] ?? 0).toDouble(),
|
||||
(sizeMap?['h'] ?? 0).toDouble(),
|
||||
);
|
||||
return model;
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +90,10 @@ class CanvasCoords {
|
|||
coords.scale = model.scale;
|
||||
coords.scrollX = model.scrollX;
|
||||
coords.scrollY = model.scrollY;
|
||||
coords.displayWidth = model.getDisplayWidth().toDouble();
|
||||
coords.displayHeight = model.getDisplayHeight().toDouble();
|
||||
coords.paddingX = model.displayPaddingX;
|
||||
coords.paddingY = model.displayPaddingY;
|
||||
coords.scrollStyle = model.scrollStyle;
|
||||
coords.size = model.size;
|
||||
return coords;
|
||||
|
|
@ -1660,7 +1680,8 @@ class InputModel {
|
|||
if (e is PointerScrollEvent) {
|
||||
final rawDx = e.scrollDelta.dx;
|
||||
final rawDy = e.scrollDelta.dy;
|
||||
final dominantDelta = rawDx.abs() > rawDy.abs() ? rawDx.abs() : rawDy.abs();
|
||||
final dominantDelta =
|
||||
rawDx.abs() > rawDy.abs() ? rawDx.abs() : rawDy.abs();
|
||||
final isSmooth = dominantDelta < 1;
|
||||
final nowUs = DateTime.now().microsecondsSinceEpoch;
|
||||
final dtUs = _lastWheelTsUs == 0 ? 0 : nowUs - _lastWheelTsUs;
|
||||
|
|
@ -2007,8 +2028,12 @@ class InputModel {
|
|||
final nearThr = 3;
|
||||
var nearRight = (canvas.size.width - x) < nearThr;
|
||||
var nearBottom = (canvas.size.height - y) < nearThr;
|
||||
final imageWidth = rect.width * canvas.scale;
|
||||
final imageHeight = rect.height * canvas.scale;
|
||||
final displayWidth =
|
||||
canvas.displayWidth > 0 ? canvas.displayWidth : rect.width;
|
||||
final displayHeight =
|
||||
canvas.displayHeight > 0 ? canvas.displayHeight : rect.height;
|
||||
final imageWidth = displayWidth * canvas.scale;
|
||||
final imageHeight = displayHeight * canvas.scale;
|
||||
if (canvas.scrollStyle != ScrollStyle.scrollauto) {
|
||||
x += imageWidth * canvas.scrollX;
|
||||
y += imageHeight * canvas.scrollY;
|
||||
|
|
@ -2036,8 +2061,19 @@ class InputModel {
|
|||
y += step;
|
||||
}
|
||||
}
|
||||
x += rect.left;
|
||||
y += rect.top;
|
||||
final paddedX = x;
|
||||
final paddedY = y;
|
||||
x = paddedX - canvas.paddingX + rect.left;
|
||||
y = paddedY - canvas.paddingY + rect.top;
|
||||
|
||||
final insidePaddedRect = paddedX >= 0 &&
|
||||
paddedY >= 0 &&
|
||||
paddedX <= displayWidth &&
|
||||
paddedY <= displayHeight;
|
||||
if (insidePaddedRect) {
|
||||
x = x.clamp(rect.left, rect.right).toDouble();
|
||||
y = y.clamp(rect.top, rect.bottom).toDouble();
|
||||
}
|
||||
|
||||
if (onExit) {
|
||||
final pos = setNearestEdge(x, y, rect);
|
||||
|
|
|
|||
|
|
@ -2185,6 +2185,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;
|
||||
|
|
@ -2222,6 +2224,86 @@ class CanvasModel with ChangeNotifier {
|
|||
ScrollStyle get scrollStyle => _scrollStyle;
|
||||
ViewStyle get viewStyle => _lastViewStyle;
|
||||
RxBool get imageOverflow => _imageOverflow;
|
||||
Rect? get realRect => parent.target?.ffiModel.rect;
|
||||
|
||||
double get remoteCanvasMargin {
|
||||
if (!supportsRemoteCanvasMargin) {
|
||||
return 0;
|
||||
}
|
||||
return _remoteCanvasMargin;
|
||||
}
|
||||
|
||||
bool get supportsRemoteCanvasMargin =>
|
||||
(isDesktop || isWebDesktop) &&
|
||||
parent.target?.connType == ConnType.defaultConn;
|
||||
|
||||
Future<void> setRemoteCanvasMargin(double value) async {
|
||||
if (!supportsRemoteCanvasMargin) {
|
||||
return;
|
||||
}
|
||||
final normalizedValue = value.clamp(0, kMaxRemoteCanvasMargin).round();
|
||||
await bind.sessionSetFlutterOption(
|
||||
sessionId: sessionId,
|
||||
k: kOptionRemoteCanvasMargin,
|
||||
v: normalizedValue.toString());
|
||||
_remoteCanvasMargin = normalizedValue.toDouble();
|
||||
_remoteCanvasMarginInitialized = true;
|
||||
await updateViewStyle();
|
||||
}
|
||||
|
||||
Future<void> initializeRemoteCanvasMargin() async {
|
||||
if (_remoteCanvasMarginInitialized || !supportsRemoteCanvasMargin) {
|
||||
return;
|
||||
}
|
||||
final sessionValue = await bind.sessionGetFlutterOption(
|
||||
sessionId: sessionId, k: kOptionRemoteCanvasMargin);
|
||||
if (_remoteCanvasMarginInitialized || !supportsRemoteCanvasMargin) {
|
||||
return;
|
||||
}
|
||||
final defaultValue =
|
||||
bind.mainGetUserDefaultOption(key: kOptionRemoteCanvasMargin);
|
||||
final value =
|
||||
sessionValue?.isNotEmpty == true ? sessionValue : defaultValue;
|
||||
_remoteCanvasMargin = (double.tryParse(value ?? '') ?? 0)
|
||||
.clamp(0, kMaxRemoteCanvasMargin)
|
||||
.toDouble();
|
||||
_remoteCanvasMarginInitialized = true;
|
||||
}
|
||||
|
||||
Rect? get paddedRect {
|
||||
final rect = realRect;
|
||||
if (rect == null) {
|
||||
return null;
|
||||
}
|
||||
final margin = remoteCanvasMargin;
|
||||
if (margin <= 0) {
|
||||
return rect;
|
||||
}
|
||||
return Rect.fromLTRB(
|
||||
rect.left - margin,
|
||||
rect.top - margin,
|
||||
rect.right + margin,
|
||||
rect.bottom + margin,
|
||||
);
|
||||
}
|
||||
|
||||
double get displayPaddingX {
|
||||
final padded = paddedRect;
|
||||
final rect = realRect;
|
||||
if (padded == null || rect == null) {
|
||||
return 0;
|
||||
}
|
||||
return rect.left - padded.left;
|
||||
}
|
||||
|
||||
double get displayPaddingY {
|
||||
final padded = paddedRect;
|
||||
final rect = realRect;
|
||||
if (padded == null || rect == null) {
|
||||
return 0;
|
||||
}
|
||||
return rect.top - padded.top;
|
||||
}
|
||||
|
||||
_resetScroll() => setScrollPercent(0.0, 0.0);
|
||||
|
||||
|
|
@ -2306,6 +2388,7 @@ class CanvasModel with ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
|
||||
await initializeRemoteCanvasMargin();
|
||||
updateSize();
|
||||
final displayWidth = getDisplayWidth();
|
||||
final displayHeight = getDisplayHeight();
|
||||
|
|
@ -2427,14 +2510,14 @@ class CanvasModel with ChangeNotifier {
|
|||
final defaultWidth = (isDesktop || isWebDesktop)
|
||||
? kDesktopDefaultDisplayWidth
|
||||
: kMobileDefaultDisplayWidth;
|
||||
return parent.target?.ffiModel.rect?.width.toInt() ?? defaultWidth;
|
||||
return paddedRect?.width.toInt() ?? defaultWidth;
|
||||
}
|
||||
|
||||
int getDisplayHeight() {
|
||||
final defaultHeight = (isDesktop || isWebDesktop)
|
||||
? kDesktopDefaultDisplayHeight
|
||||
: kMobileDefaultDisplayHeight;
|
||||
return parent.target?.ffiModel.rect?.height.toInt() ?? defaultHeight;
|
||||
return paddedRect?.height.toInt() ?? defaultHeight;
|
||||
}
|
||||
|
||||
static double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
|
||||
|
|
@ -2684,6 +2767,8 @@ class CanvasModel with ChangeNotifier {
|
|||
_y = 0;
|
||||
_scale = 1.0;
|
||||
_lastViewStyle = ViewStyle.defaultViewStyle();
|
||||
_remoteCanvasMargin = 0;
|
||||
_remoteCanvasMarginInitialized = false;
|
||||
_timerMobileFocusCanvasCursor?.cancel();
|
||||
_timerMobileRestoreCanvasOffset?.cancel();
|
||||
_offsetBeforeMobileSoftKeyboard = null;
|
||||
|
|
@ -3017,8 +3102,10 @@ class CursorModel with ChangeNotifier {
|
|||
ui.Image? get image => _image;
|
||||
CursorData? get cache => _cache;
|
||||
|
||||
double get x => _x - _displayOriginX;
|
||||
double get y => _y - _displayOriginY;
|
||||
double get x =>
|
||||
_x - _displayOriginX + (parent.target?.canvasModel.displayPaddingX ?? 0);
|
||||
double get y =>
|
||||
_y - _displayOriginY + (parent.target?.canvasModel.displayPaddingY ?? 0);
|
||||
|
||||
double get devicePixelRatio => parent.target!.canvasModel.devicePixelRatio;
|
||||
|
||||
|
|
@ -3055,8 +3142,12 @@ class CursorModel with ChangeNotifier {
|
|||
final xoffset = parent.target?.canvasModel.x ?? 0;
|
||||
final yoffset = parent.target?.canvasModel.y ?? 0;
|
||||
final scale = parent.target?.canvasModel.scale ?? 1;
|
||||
final x0 = _displayOriginX - xoffset / scale;
|
||||
final y0 = _displayOriginY - yoffset / scale;
|
||||
final x0 = _displayOriginX -
|
||||
(parent.target?.canvasModel.displayPaddingX ?? 0) -
|
||||
xoffset / scale;
|
||||
final y0 = _displayOriginY -
|
||||
(parent.target?.canvasModel.displayPaddingY ?? 0) -
|
||||
yoffset / scale;
|
||||
return Rect.fromLTWH(x0, y0, size.width / scale, size.height / scale);
|
||||
}
|
||||
|
||||
|
|
@ -3067,10 +3158,14 @@ class CursorModel with ChangeNotifier {
|
|||
// See `getVisibleRect()`
|
||||
// _x = _displayOriginX - xoffset / scale + size.width / scale * 0.5;
|
||||
// _y = _displayOriginY - yoffset / scale + size.height / scale * 0.5;
|
||||
final displayOriginX =
|
||||
_displayOriginX - (parent.target?.canvasModel.displayPaddingX ?? 0);
|
||||
final displayOriginY =
|
||||
_displayOriginY - (parent.target?.canvasModel.displayPaddingY ?? 0);
|
||||
final size = parent.target?.canvasModel.getSize() ??
|
||||
MediaQueryData.fromView(ui.window).size;
|
||||
final xoffset = (_displayOriginX - _x) * scale + size.width * 0.5;
|
||||
final yoffset = (_displayOriginY - _y) * scale + size.height * 0.5;
|
||||
final xoffset = (displayOriginX - _x) * scale + size.width * 0.5;
|
||||
final yoffset = (displayOriginY - _y) * scale + size.height * 0.5;
|
||||
return Offset(xoffset, yoffset);
|
||||
}
|
||||
|
||||
|
|
@ -3186,11 +3281,10 @@ class CursorModel with ChangeNotifier {
|
|||
var cx = r.center.dx;
|
||||
var cy = r.center.dy;
|
||||
var tryMoveCanvasX = false;
|
||||
final displayRect = parent.target?.ffiModel.rect;
|
||||
final displayRect = parent.target?.canvasModel.paddedRect;
|
||||
if (dx > 0) {
|
||||
final maxCanvasCanMove = _displayOriginX +
|
||||
(displayRect?.width ?? 1280) -
|
||||
r.right.roundToDouble();
|
||||
final maxCanvasCanMove =
|
||||
(displayRect?.right ?? 1280) - r.right.roundToDouble();
|
||||
tryMoveCanvasX = _x + dx > cx && maxCanvasCanMove > 0;
|
||||
if (tryMoveCanvasX) {
|
||||
dx = min(dx, maxCanvasCanMove);
|
||||
|
|
@ -3199,7 +3293,8 @@ class CursorModel with ChangeNotifier {
|
|||
dx = min(dx, maxCursorCanMove);
|
||||
}
|
||||
} else if (dx < 0) {
|
||||
final maxCanvasCanMove = _displayOriginX - r.left.roundToDouble();
|
||||
final maxCanvasCanMove =
|
||||
(displayRect?.left ?? 0) - r.left.roundToDouble();
|
||||
tryMoveCanvasX = _x + dx < cx && maxCanvasCanMove < 0;
|
||||
if (tryMoveCanvasX) {
|
||||
dx = max(dx, maxCanvasCanMove);
|
||||
|
|
@ -3210,9 +3305,8 @@ class CursorModel with ChangeNotifier {
|
|||
}
|
||||
var tryMoveCanvasY = false;
|
||||
if (dy > 0) {
|
||||
final mayCanvasCanMove = _displayOriginY +
|
||||
(displayRect?.height ?? 720) -
|
||||
r.bottom.roundToDouble();
|
||||
final mayCanvasCanMove =
|
||||
(displayRect?.bottom ?? 720) - r.bottom.roundToDouble();
|
||||
tryMoveCanvasY = _y + dy > cy && mayCanvasCanMove > 0;
|
||||
if (tryMoveCanvasY) {
|
||||
dy = min(dy, mayCanvasCanMove);
|
||||
|
|
@ -3221,7 +3315,7 @@ class CursorModel with ChangeNotifier {
|
|||
dy = min(dy, mayCursorCanMove);
|
||||
}
|
||||
} else if (dy < 0) {
|
||||
final mayCanvasCanMove = _displayOriginY - r.top.roundToDouble();
|
||||
final mayCanvasCanMove = (displayRect?.top ?? 0) - r.top.roundToDouble();
|
||||
tryMoveCanvasY = _y + dy < cy && mayCanvasCanMove < 0;
|
||||
if (tryMoveCanvasY) {
|
||||
dy = max(dy, mayCanvasCanMove);
|
||||
|
|
@ -3232,6 +3326,8 @@ class CursorModel with ChangeNotifier {
|
|||
}
|
||||
|
||||
if (dx == 0 && dy == 0) return;
|
||||
final canvasDx = dx;
|
||||
final canvasDy = dy;
|
||||
|
||||
Point<double>? newPos;
|
||||
final rect = parent.target?.ffiModel.rect;
|
||||
|
|
@ -3249,17 +3345,25 @@ class CursorModel with ChangeNotifier {
|
|||
rect,
|
||||
buttons: kPrimaryButton);
|
||||
if (newPos == null) {
|
||||
if (tryMoveCanvasX && canvasDx != 0) {
|
||||
parent.target?.canvasModel.panX(-canvasDx * scale);
|
||||
}
|
||||
if (tryMoveCanvasY && canvasDy != 0) {
|
||||
parent.target?.canvasModel.panY(-canvasDy * scale);
|
||||
}
|
||||
return;
|
||||
}
|
||||
dx = newPos.x - _x;
|
||||
dy = newPos.y - _y;
|
||||
_x = newPos.x;
|
||||
_y = newPos.y;
|
||||
if (tryMoveCanvasX && dx != 0) {
|
||||
parent.target?.canvasModel.panX(-dx * scale);
|
||||
final panDx = dx != 0 ? dx : canvasDx;
|
||||
final panDy = dy != 0 ? dy : canvasDy;
|
||||
if (tryMoveCanvasX && panDx != 0) {
|
||||
parent.target?.canvasModel.panX(-panDx * scale);
|
||||
}
|
||||
if (tryMoveCanvasY && dy != 0) {
|
||||
parent.target?.canvasModel.panY(-dy * scale);
|
||||
if (tryMoveCanvasY && panDy != 0) {
|
||||
parent.target?.canvasModel.panY(-panDy * scale);
|
||||
}
|
||||
|
||||
parent.target?.inputModel.moveMouse(_x, _y);
|
||||
|
|
|
|||
271
flutter/test/canvas_margin_test.dart
Normal file
271
flutter/test/canvas_margin_test.dart
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
const _nearEdgeThreshold = 3.0;
|
||||
|
||||
// Minimal test fixtures for the pure canvas-margin math. These intentionally
|
||||
// cover only the fields used by pointer mapping.
|
||||
enum ScrollStyle { scrollbar, scrollauto }
|
||||
|
||||
class CanvasCoords {
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
double scale = 1.0;
|
||||
double scrollX = 0;
|
||||
double scrollY = 0;
|
||||
double displayWidth = 0;
|
||||
double displayHeight = 0;
|
||||
double paddingX = 0;
|
||||
double paddingY = 0;
|
||||
ScrollStyle scrollStyle = ScrollStyle.scrollauto;
|
||||
Size size = Size.zero;
|
||||
}
|
||||
|
||||
double clampMargin(double value) {
|
||||
return min(kMaxRemoteCanvasMargin, max(0.0, value));
|
||||
}
|
||||
|
||||
int normalizeMarginForStorage(double value) {
|
||||
return value.clamp(0, kMaxRemoteCanvasMargin).round();
|
||||
}
|
||||
|
||||
Rect? computePaddedRect(Rect? realRect, double margin) {
|
||||
if (realRect == null) return null;
|
||||
if (margin <= 0) return realRect;
|
||||
return Rect.fromLTRB(
|
||||
realRect.left - margin,
|
||||
realRect.top - margin,
|
||||
realRect.right + margin,
|
||||
realRect.bottom + margin,
|
||||
);
|
||||
}
|
||||
|
||||
double computeDisplayPaddingX(Rect? paddedRect, Rect? realRect) {
|
||||
if (paddedRect == null || realRect == null) return 0;
|
||||
return realRect.left - paddedRect.left;
|
||||
}
|
||||
|
||||
double computeDisplayPaddingY(Rect? paddedRect, Rect? realRect) {
|
||||
if (paddedRect == null || realRect == null) return 0;
|
||||
return realRect.top - paddedRect.top;
|
||||
}
|
||||
|
||||
double computeAdaptiveScale({
|
||||
required double viewWidth,
|
||||
required double viewHeight,
|
||||
required int displayWidth,
|
||||
required int displayHeight,
|
||||
}) {
|
||||
if (viewWidth == 0 ||
|
||||
viewHeight == 0 ||
|
||||
displayWidth == 0 ||
|
||||
displayHeight == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
return min(viewWidth / displayWidth, viewHeight / displayHeight);
|
||||
}
|
||||
|
||||
(double, double) computeCanvasOffset(
|
||||
Size viewSize, int displayWidth, int displayHeight, double scale) {
|
||||
final x = (viewSize.width - displayWidth * scale) / 2;
|
||||
final y = (viewSize.height - displayHeight * scale) / 2;
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
(double, double)? computePointerPosition({
|
||||
required double pointerX,
|
||||
required double pointerY,
|
||||
required CanvasCoords canvas,
|
||||
required Rect remoteRect,
|
||||
}) {
|
||||
double x = pointerX;
|
||||
double y = pointerY;
|
||||
|
||||
final nearRight = (canvas.size.width - x) < _nearEdgeThreshold;
|
||||
final nearBottom = (canvas.size.height - y) < _nearEdgeThreshold;
|
||||
final displayWidth =
|
||||
canvas.displayWidth > 0 ? canvas.displayWidth : remoteRect.width;
|
||||
final displayHeight =
|
||||
canvas.displayHeight > 0 ? canvas.displayHeight : remoteRect.height;
|
||||
final imageWidth = displayWidth * canvas.scale;
|
||||
final imageHeight = displayHeight * canvas.scale;
|
||||
|
||||
if (canvas.scrollStyle != ScrollStyle.scrollauto) {
|
||||
x += imageWidth * canvas.scrollX;
|
||||
y += imageHeight * canvas.scrollY;
|
||||
|
||||
if (canvas.size.width > imageWidth) {
|
||||
x -= ((canvas.size.width - imageWidth) / 2);
|
||||
}
|
||||
if (canvas.size.height > imageHeight) {
|
||||
y -= ((canvas.size.height - imageHeight) / 2);
|
||||
}
|
||||
} else {
|
||||
x -= canvas.x;
|
||||
y -= canvas.y;
|
||||
}
|
||||
|
||||
x /= canvas.scale;
|
||||
y /= canvas.scale;
|
||||
if (canvas.scale > 0 && canvas.scale < 1) {
|
||||
final step = 1.0 / canvas.scale - 1;
|
||||
if (nearRight) {
|
||||
x += step;
|
||||
}
|
||||
if (nearBottom) {
|
||||
y += step;
|
||||
}
|
||||
}
|
||||
|
||||
final paddedX = x;
|
||||
final paddedY = y;
|
||||
x = paddedX - canvas.paddingX + remoteRect.left;
|
||||
y = paddedY - canvas.paddingY + remoteRect.top;
|
||||
|
||||
final insidePaddedRect = paddedX >= 0 &&
|
||||
paddedY >= 0 &&
|
||||
paddedX <= displayWidth &&
|
||||
paddedY <= displayHeight;
|
||||
if (insidePaddedRect) {
|
||||
x = x.clamp(remoteRect.left, remoteRect.right).toDouble();
|
||||
y = y.clamp(remoteRect.top, remoteRect.bottom).toDouble();
|
||||
}
|
||||
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Remote canvas margin math', () {
|
||||
test('clamps and normalizes margin values', () {
|
||||
expect(clampMargin(-10), 0);
|
||||
expect(clampMargin(50), 50);
|
||||
expect(clampMargin(999), kMaxRemoteCanvasMargin);
|
||||
|
||||
expect(normalizeMarginForStorage(50.7), 51);
|
||||
expect(normalizeMarginForStorage(-5), 0);
|
||||
expect(normalizeMarginForStorage(999), kMaxRemoteCanvasMargin.toInt());
|
||||
});
|
||||
|
||||
test('expands remote rect and derives display padding', () {
|
||||
final realRect = Rect.fromLTWH(0, 0, 1920, 1080);
|
||||
final paddedRect = computePaddedRect(realRect, 100)!;
|
||||
|
||||
expect(paddedRect, Rect.fromLTRB(-100, -100, 2020, 1180));
|
||||
expect(computeDisplayPaddingX(paddedRect, realRect), 100);
|
||||
expect(computeDisplayPaddingY(paddedRect, realRect), 100);
|
||||
});
|
||||
|
||||
test('margin-expanded display affects adaptive scale and centering', () {
|
||||
final realRect = Rect.fromLTWH(0, 0, 1920, 1080);
|
||||
final paddedRect = computePaddedRect(realRect, 100)!;
|
||||
final displayWidth = paddedRect.width.toInt();
|
||||
final displayHeight = paddedRect.height.toInt();
|
||||
final scale = computeAdaptiveScale(
|
||||
viewWidth: 1920,
|
||||
viewHeight: 1080,
|
||||
displayWidth: displayWidth,
|
||||
displayHeight: displayHeight,
|
||||
);
|
||||
final (x, y) = computeCanvasOffset(
|
||||
Size(1920, 1080), displayWidth, displayHeight, scale);
|
||||
|
||||
expect(scale, closeTo(1080 / 1280, 0.0001));
|
||||
expect(x, closeTo((1920 - 2120 * scale) / 2, 0.01));
|
||||
expect(y, closeTo(0, 0.01));
|
||||
});
|
||||
});
|
||||
|
||||
group('Pointer coordinate transforms', () {
|
||||
test('no margin maps pointer directly in scrollauto mode', () {
|
||||
final canvas = CanvasCoords()
|
||||
..scale = 1.0
|
||||
..displayWidth = 1920
|
||||
..displayHeight = 1080
|
||||
..size = Size(1920, 1080);
|
||||
final remoteRect = Rect.fromLTWH(0, 0, 1920, 1080);
|
||||
|
||||
final result = computePointerPosition(
|
||||
pointerX: 960, pointerY: 540, canvas: canvas, remoteRect: remoteRect);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.$1, closeTo(960, 0.01));
|
||||
expect(result.$2, closeTo(540, 0.01));
|
||||
});
|
||||
|
||||
test('margin padding offsets pointer coordinates', () {
|
||||
final canvas = CanvasCoords()
|
||||
..displayWidth = 2120
|
||||
..displayHeight = 1280
|
||||
..paddingX = 100
|
||||
..paddingY = 100
|
||||
..scale = 1.0
|
||||
..size = Size(2120, 1280);
|
||||
final remoteRect = Rect.fromLTWH(0, 0, 1920, 1080);
|
||||
|
||||
final result = computePointerPosition(
|
||||
pointerX: 100, pointerY: 100, canvas: canvas, remoteRect: remoteRect);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.$1, closeTo(0, 0.01));
|
||||
expect(result.$2, closeTo(0, 0.01));
|
||||
});
|
||||
|
||||
test('pointer in margin area clamps to remote rect boundary', () {
|
||||
final canvas = CanvasCoords()
|
||||
..displayWidth = 2120
|
||||
..displayHeight = 1280
|
||||
..paddingX = 100
|
||||
..paddingY = 100
|
||||
..scale = 1.0
|
||||
..size = Size(2120, 1280);
|
||||
final remoteRect = Rect.fromLTWH(0, 0, 1920, 1080);
|
||||
|
||||
final result = computePointerPosition(
|
||||
pointerX: 50, pointerY: 50, canvas: canvas, remoteRect: remoteRect);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.$1, closeTo(0, 0.01));
|
||||
expect(result.$2, closeTo(0, 0.01));
|
||||
});
|
||||
|
||||
test('margin and adaptive scale map view center to remote center', () {
|
||||
final scale = 1080.0 / 2360;
|
||||
final canvas = CanvasCoords()
|
||||
..displayWidth = 4040
|
||||
..displayHeight = 2360
|
||||
..paddingX = 100
|
||||
..paddingY = 100
|
||||
..scale = scale
|
||||
..x = (1920 - 4040 * scale) / 2
|
||||
..y = (1080 - 2360 * scale) / 2
|
||||
..size = Size(1920, 1080);
|
||||
final remoteRect = Rect.fromLTWH(0, 0, 3840, 2160);
|
||||
|
||||
final result = computePointerPosition(
|
||||
pointerX: 960, pointerY: 540, canvas: canvas, remoteRect: remoteRect);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.$1, closeTo(1920, 1));
|
||||
expect(result.$2, closeTo(1080, 1));
|
||||
});
|
||||
|
||||
test('zoomed-out near edge applies edge correction', () {
|
||||
final canvas = CanvasCoords()
|
||||
..scale = 0.5
|
||||
..displayWidth = 200
|
||||
..displayHeight = 200
|
||||
..size = Size(100, 100);
|
||||
final remoteRect = Rect.fromLTWH(0, 0, 200, 200);
|
||||
|
||||
final result = computePointerPosition(
|
||||
pointerX: 99, pointerY: 99, canvas: canvas, remoteRect: remoteRect);
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result!.$1, closeTo(199, 0.01));
|
||||
expect(result.$2, closeTo(199, 0.01));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "阻止用户输入"),
|
||||
("Unblock user input", "取消阻止用户输入"),
|
||||
("Adjust Window", "调节窗口"),
|
||||
("canvas_margin", "画布边距"),
|
||||
("Original", "原始比例"),
|
||||
("Shrink", "收缩"),
|
||||
("Stretch", "伸展"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Bloker brugerinput"),
|
||||
("Unblock user input", "Fjern blokering af brugerinput"),
|
||||
("Adjust Window", "Juster vinduet"),
|
||||
("canvas_margin", "Lærredsmargen"),
|
||||
("Original", "Original"),
|
||||
("Shrink", "Krymp"),
|
||||
("Stretch", "Stræk ud"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Benutzereingaben blockieren"),
|
||||
("Unblock user input", "Benutzereingaben freigeben"),
|
||||
("Adjust Window", "Fenster anpassen"),
|
||||
("canvas_margin", "Leinwandrand"),
|
||||
("Original", "Original"),
|
||||
("Shrink", "Verkleinern"),
|
||||
("Stretch", "Strecken"),
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Control Actions", "Control actions"),
|
||||
("Display Settings", "Display settings"),
|
||||
("Image Quality", "Image quality"),
|
||||
("canvas_margin", "Canvas margin"),
|
||||
("Scroll Style", "Scroll style"),
|
||||
("Show Toolbar", "Show toolbar"),
|
||||
("Hide Toolbar", "Hide toolbar"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Bloquear entrada de usuario"),
|
||||
("Unblock user input", "Desbloquear entrada de usuario"),
|
||||
("Adjust Window", "Ajustar ventana"),
|
||||
("canvas_margin", "Margen del lienzo"),
|
||||
("Original", "Original"),
|
||||
("Shrink", "Encoger"),
|
||||
("Stretch", "Estirar"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Estä käyttäjän toiminta"),
|
||||
("Unblock user input", "Salli käyttäjän toiminta"),
|
||||
("Adjust Window", "Sovita ikkuna"),
|
||||
("canvas_margin", "Piirtoalueen marginaali"),
|
||||
("Original", "Alkuperäinen"),
|
||||
("Shrink", "Pienennä"),
|
||||
("Stretch", "Venytä"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Bloquer la saisie de l’utilisateur"),
|
||||
("Unblock user input", "Débloquer la saisie de l’utilisateur"),
|
||||
("Adjust Window", "Ajuster la fenêtre"),
|
||||
("canvas_margin", "Marge du canevas"),
|
||||
("Original", "Ratio d'origine"),
|
||||
("Shrink", "Rétrécir"),
|
||||
("Stretch", "Étirer"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Blocca input utente"),
|
||||
("Unblock user input", "Sblocca input utente"),
|
||||
("Adjust Window", "Adatta finestra"),
|
||||
("canvas_margin", "Margine della tela"),
|
||||
("Original", "Originale"),
|
||||
("Shrink", "Restringi"),
|
||||
("Stretch", "Allarga"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "ユーザーの入力をブロック"),
|
||||
("Unblock user input", "ユーザーの入力を許可"),
|
||||
("Adjust Window", "ウィンドウを調整"),
|
||||
("canvas_margin", "キャンバス余白"),
|
||||
("Original", "オリジナル"),
|
||||
("Shrink", "縮小"),
|
||||
("Stretch", "伸縮"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "사용자 입력 차단"),
|
||||
("Unblock user input", "사용자 입력 차단 해제"),
|
||||
("Adjust Window", "창 크기 조정"),
|
||||
("canvas_margin", "캔버스 여백"),
|
||||
("Original", "원본"),
|
||||
("Shrink", "축소"),
|
||||
("Stretch", "늘이기"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Blokker brukerinput"),
|
||||
("Unblock user input", "Fjern blokkering av brukerinput"),
|
||||
("Adjust Window", "Juster vinduet"),
|
||||
("canvas_margin", "Lerretsmarg"),
|
||||
("Original", "Original"),
|
||||
("Shrink", "Krymp"),
|
||||
("Stretch", "Strekk ut"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Gebruikersinvoer blokkeren"),
|
||||
("Unblock user input", "Gebruikersinvoer deblokkeren"),
|
||||
("Adjust Window", "Venster Aanpassen"),
|
||||
("canvas_margin", "Canvasmarge"),
|
||||
("Original", "Origineel"),
|
||||
("Shrink", "Verkleinen"),
|
||||
("Stretch", "Uitrekken"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Blokuj peryferia użytkownika"),
|
||||
("Unblock user input", "Odblokuj peryferia użytkownika"),
|
||||
("Adjust Window", "Dostosuj okno"),
|
||||
("canvas_margin", "Margines płótna"),
|
||||
("Original", "Oryginalny"),
|
||||
("Shrink", "Zmniejsz"),
|
||||
("Stretch", "Rozciągnij"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Bloquear entrada de utilizador"),
|
||||
("Unblock user input", "Desbloquear entrada de utilizador"),
|
||||
("Adjust Window", "Ajustar Janela"),
|
||||
("canvas_margin", "Margem do ecrã"),
|
||||
("Original", "Original"),
|
||||
("Shrink", "Reduzir"),
|
||||
("Stretch", "Aumentar"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "Bloquear entrada do usuário"),
|
||||
("Unblock user input", "Desbloquear entrada do usuário"),
|
||||
("Adjust Window", "Ajustar Janela"),
|
||||
("canvas_margin", "Margem da tela"),
|
||||
("Original", "Original"),
|
||||
("Shrink", "Reduzir"),
|
||||
("Stretch", "Aumentar"),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", ""),
|
||||
("Unblock user input", ""),
|
||||
("Adjust Window", ""),
|
||||
("canvas_margin", ""),
|
||||
("Original", ""),
|
||||
("Shrink", ""),
|
||||
("Stretch", ""),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("Block user input", "封鎖使用者輸入"),
|
||||
("Unblock user input", "取消封鎖使用者輸入"),
|
||||
("Adjust Window", "調整視窗"),
|
||||
("canvas_margin", "畫布邊距"),
|
||||
("Original", "原始"),
|
||||
("Shrink", "縮減"),
|
||||
("Stretch", "延展"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue