mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 22:11:30 +08:00
feat(ui): custom scale mode with inline controls and live apply (#13045)
* feat(ui): custom scale mode with inline controls and live apply Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Update flutter/lib/models/model.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/desktop/widgets/remote_toolbar.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor(dialog): remove unused showCustomScaleDialog function Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * feat(ui): enhance custom scale controls with live updates and improved UI - Introduced a reactive custom scale percentage using RxInt. - Added initialization of custom scale from stored options after the widget builds. - Updated viewStyle method to conditionally display custom controls based on selection. - Implemented a debouncer for smoother scale adjustments. - Enhanced slider UI with custom thumb shape and improved button interactions. This update improves user experience by allowing real-time adjustments to the custom scale settings. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(remote_toolbar): improve widget lifecycle management and enhance slider dimensions - Moved initialization of custom scale percentage to initState for better lifecycle handling. - Updated slider thumb dimensions and layout for improved UI consistency. - Added dispose method to clean up resources in custom scale controls. These changes enhance the overall performance and user experience of the remote toolbar. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * feat(remote_toolbar): enhance scroll behavior and improve slider thumb rendering - Introduced a new state variable to manage scroll enablement based on canvas model changes. - Updated the return value of the viewStyle method to include the scroll enablement status. - Refactored the slider thumb shape for better performance and visual consistency. - Improved the initialization of image overflow detection in the CanvasModel. These changes enhance the user experience by providing dynamic scroll control and a more responsive UI. 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> * refactor(scale): introduce utility functions for custom scale management for DRY - Added a new file `scale.dart` containing utility functions to clamp, parse, and compute custom scale percentages. - Refactored the `CanvasModel` and `_DisplayMenuState` to utilize the new utility functions for fetching and applying custom scale settings. - Improved code readability and maintainability by centralizing scale-related logic. These changes enhance the handling of custom scale settings across the application. Signed-off-by: Alessandro De Blasis alex@deblasis.net Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Update flutter/lib/utils/scale.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/models/model.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/desktop/widgets/remote_toolbar.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/desktop/widgets/remote_toolbar.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/desktop/widgets/remote_toolbar.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/models/model.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: Remove unused import of 'uuid' in scale.dart Signed-off-by: Alessandro De Blasis alex@deblasis.net Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * feat(remote_toolbar): implement nonlinear mapping for custom scale slider - Added piecewise mapping functions to convert normalized slider positions to custom scale percentages and vice versa. - Introduced snapping behavior for the slider to enhance user experience. - Updated the slider's minimum and maximum values to align with the new mapping logic. - Adjusted the clamping function to ensure the minimum percentage is 10. These changes improve the precision and usability of the custom scale slider in the remote toolbar. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * fix(scale): update minimum scale percentage to 5 - Adjusted the minimum scale percentage in both the remote toolbar and the clamping function to improve consistency and usability. - This change aligns the clamping logic with the updated minimum value for the custom scale slider. 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> * refactor(scale): centralize custom scale constants in consts.dart - Moved piecewise mapping constants for the custom scale slider from the remote toolbar to consts.dart for better organization and maintainability. - Introduced additional constants related to custom scale behavior, including minimum, pivot, and maximum percentages, as well as debounce duration. - Updated the remote toolbar to reference these centralized constants, improving code clarity and reducing duplication. These changes enhance the structure and readability of the custom scale implementation. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(consts): remove duplicate custom scale percent key definition - Eliminated redundant declaration of the custom scale percent key in consts.dart, ensuring a single source of truth for this constant. - This change improves code clarity and maintainability by reducing duplication. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(scale): update clamping logic to use centralized constants - Modified the clamping function to utilize the newly defined constants for minimum and maximum scale percentages, enhancing code maintainability and clarity. - This change ensures consistency across the application by referencing a single source for scale limits. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Enhance RdoMenuButton behavior for custom scale selection - Updated the RdoMenuButton to include a new `closeOnActivate` parameter, allowing the submenu to remain open when selecting custom scale options. - Modified the onChanged callback to conditionally trigger a rebuild when entering custom mode, improving user experience by immediately displaying the slider controls. These changes streamline the interaction with the custom scale feature in the remote toolbar. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(toolbar): _DisplayMenuState to simplify scroll handling - Removed the _scrollEnabled state variable and its associated logic, streamlining the component's state management. - Updated the RdoMenuButton onChanged callbacks to directly reference the canvasModel's imageOverflow value, enhancing responsiveness and reducing complexity. These changes improve code clarity and maintainability in the remote toolbar's display menu. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * feat(lang): Add translations for custom scale features in multiple languages - Introduced new entries for "Scale custom", "Custom scale slider", "Decrease", and "Increase" in various language files to support the custom scale functionality. - This update enhances the localization of the application, ensuring users can interact with the custom scale features in their preferred language. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * feat(lang): Add translations for custom scale features in Catalan and Romanian - Updated language files for Catalan and Romanian to include translations for "Custom scale slider", "Decrease", and "Increase". - This enhancement improves the localization of the application, allowing users to interact with custom scale features in their native languages. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * fix(model): Correct error logging in getSessionCustomScale method - Updated the error logging statement in the getSessionCustomScale method to properly interpolate the exception message, improving debugging clarity. - This change ensures that error messages are more informative, aiding in troubleshooting issues related to session scaling. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(scale): Simplify clamping logic for custom scale percent - Updated the clampCustomScalePercent function to use the built-in clamp method, improving code readability and maintainability. - This change ensures consistent clamping behavior across the application by centralizing the logic for valid scale ranges. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(scale): Remove unused import for web bridge - Eliminated the conditional import of the web bridge from scale.dart, as it is no longer necessary. This change helps to clean up the code and improve maintainability by removing unused dependencies. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * chore(model): typo 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> * chore(toolbar): Clarify precision for scale adjustments in remote toolbar - Added comments to clarify the use of a wide range of divisions for the scale slider, allowing for ~1% precision increments. This change improves user experience by enabling more precise scale value settings, reducing the need for fine-tuning with +/- buttons. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * Update flutter/lib/models/model.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update flutter/lib/desktop/widgets/remote_toolbar.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix(model): Enhance error logging in getSessionCustomScale method - Improved error logging by adding stack trace output to debugPrintStack, enhancing debugging capabilities for session scaling issues. - This change provides clearer insights into errors encountered during scale retrieval, aiding in troubleshooting. Signed-off-by: Alessandro De Blasis <alex@deblasis.net> * refactor(toolbar): Simplify custom scale percent retrieval in remote toolbar - Replaced the previous method of retrieving the custom scale percent with a new function, getSessionCustomScalePercent, enhancing code clarity and maintainability. - This change streamlines the process of obtaining the scale value, ensuring a more efficient and readable implementation. 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> --------- 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> Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a3637cf2b6
commit
482840b8bb
@@ -363,6 +363,11 @@ Future<List<TRadioMenu<String>>> toolbarViewStyle(
|
||||
child: Text(translate('Scale adaptive')),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged),
|
||||
TRadioMenu<String>(
|
||||
child: Text(translate('Scale custom')),
|
||||
value: kRemoteViewStyleCustom,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -313,6 +313,10 @@ const kRemoteViewStyleOriginal = 'original';
|
||||
/// [kRemoteViewStyleAdaptive] Show remote image scaling by ratio factor.
|
||||
const kRemoteViewStyleAdaptive = 'adaptive';
|
||||
|
||||
/// [kRemoteViewStyleCustom] Show remote image at a user-defined scale percent.
|
||||
const kRemoteViewStyleCustom = 'custom';
|
||||
|
||||
|
||||
/// [kRemoteScrollStyleAuto] Scroll image auto by position.
|
||||
const kRemoteScrollStyleAuto = 'scrollauto';
|
||||
|
||||
@@ -345,6 +349,15 @@ const Set<PointerDeviceKind> kTouchBasedDeviceKinds = {
|
||||
PointerDeviceKind.invertedStylus,
|
||||
};
|
||||
|
||||
// Scale custom related constants
|
||||
const String kCustomScalePercentKey = 'custom_scale_percent'; // Flutter option key for storing custom scale percent (integer 5-1000)
|
||||
const int kScaleCustomMinPercent = 5;
|
||||
const int kScaleCustomPivotPercent = 100; // 100% should be at 1/3 of track
|
||||
const int kScaleCustomMaxPercent = 1000;
|
||||
const double kScaleCustomPivotPos = 1.0 / 3.0; // first 1/3 → up to 100%
|
||||
const double kScaleCustomDetentEpsilon = 0.006; // snap range around pivot (~0.6%)
|
||||
const Duration kDebounceCustomScaleDuration = Duration(milliseconds: 300);
|
||||
|
||||
// ================================ mobile ================================
|
||||
|
||||
// Magic numbers, maybe need to avoid it or use a better way to get them.
|
||||
|
||||
@@ -25,6 +25,7 @@ import '../../models/platform_model.dart';
|
||||
import '../../common/shared_state.dart';
|
||||
import './popup_menu.dart';
|
||||
import './kb_layout_type_chooser.dart';
|
||||
import 'package:flutter_hbb/utils/scale.dart';
|
||||
|
||||
class ToolbarState {
|
||||
late RxBool _pin;
|
||||
@@ -175,6 +176,12 @@ class RemoteMenuEntry {
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale custom'),
|
||||
value: kRemoteViewStyleCustom,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
@@ -1024,6 +1031,7 @@ class _DisplayMenu extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
final RxInt _customPercent = 100.obs;
|
||||
late final ScreenAdjustor _screenAdjustor = ScreenAdjustor(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
@@ -1037,13 +1045,27 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
FFI get ffi => widget.ffi;
|
||||
String get id => widget.id;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize custom percent from stored option once
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
try {
|
||||
final v = await getSessionCustomScalePercent(widget.ffi.sessionId);
|
||||
if (_customPercent.value != v) {
|
||||
_customPercent.value = v;
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_screenAdjustor.updateScreen();
|
||||
menuChildrenGetter() {
|
||||
final menuChildren = <Widget>[
|
||||
_screenAdjustor.adjustWindow(context),
|
||||
viewStyle(),
|
||||
viewStyle(customPercent: _customPercent),
|
||||
scrollStyle(),
|
||||
imageQuality(),
|
||||
codec(),
|
||||
@@ -1108,30 +1130,69 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
);
|
||||
}
|
||||
|
||||
viewStyle() {
|
||||
viewStyle({required RxInt customPercent}) {
|
||||
return futureBuilder(
|
||||
future: toolbarViewStyle(context, widget.id, widget.ffi),
|
||||
hasData: (data) {
|
||||
final v = data as List<TRadioMenu<String>>;
|
||||
final bool isCustomSelected = v.isNotEmpty
|
||||
? v.first.groupValue == kRemoteViewStyleCustom
|
||||
: false;
|
||||
return Column(children: [
|
||||
...v
|
||||
.map((e) => RdoMenuButton<String>(
|
||||
value: e.value,
|
||||
groupValue: e.groupValue,
|
||||
onChanged: e.onChanged,
|
||||
child: e.child,
|
||||
ffi: ffi))
|
||||
.toList(),
|
||||
Divider(),
|
||||
...v.map((e) {
|
||||
final isCustom = e.value == kRemoteViewStyleCustom;
|
||||
final child = isCustom
|
||||
? Text(translate('Scale custom'))
|
||||
: e.child;
|
||||
// Whether the current selection is already custom
|
||||
final bool isGroupCustomSelected =
|
||||
e.groupValue == kRemoteViewStyleCustom;
|
||||
// Keep menu open when switching INTO custom so the slider is visible immediately
|
||||
final bool keepOpenForThisItem = isCustom && !isGroupCustomSelected;
|
||||
return RdoMenuButton<String>(
|
||||
value: e.value,
|
||||
groupValue: e.groupValue,
|
||||
onChanged: (value) {
|
||||
// Perform the original change
|
||||
e.onChanged?.call(value);
|
||||
// Only force a rebuild when we keep the menu open to reveal the slider
|
||||
if (keepOpenForThisItem) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
ffi: ffi,
|
||||
// When entering custom, keep submenu open to show the slider controls
|
||||
closeOnActivate: !keepOpenForThisItem);
|
||||
}).toList(),
|
||||
// Only show a divider when custom is NOT selected
|
||||
if (!isCustomSelected) Divider(),
|
||||
_customControlsIfCustomSelected(onChanged: (v) => customPercent.value = v),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _customControlsIfCustomSelected({ValueChanged<int>? onChanged}) {
|
||||
return futureBuilder(future: () async {
|
||||
final current = await bind.sessionGetViewStyle(sessionId: ffi.sessionId);
|
||||
return current == kRemoteViewStyleCustom;
|
||||
}(), hasData: (data) {
|
||||
final isCustom = data as bool;
|
||||
return AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 220),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: isCustom ? _CustomScaleMenuControls(ffi: ffi, onChanged: onChanged) : SizedBox.shrink(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
scrollStyle() {
|
||||
return futureBuilder(future: () async {
|
||||
final viewStyle =
|
||||
await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? '';
|
||||
final visible = viewStyle == kRemoteViewStyleOriginal;
|
||||
final visible = viewStyle == kRemoteViewStyleOriginal ||
|
||||
viewStyle == kRemoteViewStyleCustom;
|
||||
final scrollStyle =
|
||||
await bind.sessionGetScrollStyle(sessionId: ffi.sessionId) ?? '';
|
||||
return {'visible': visible, 'scrollStyle': scrollStyle};
|
||||
@@ -1146,24 +1207,27 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
widget.ffi.canvasModel.updateScrollStyle();
|
||||
}
|
||||
|
||||
final enabled = widget.ffi.canvasModel.imageOverflow.value;
|
||||
return Column(children: [
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('ScrollAuto')),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
groupValue: groupValue,
|
||||
onChanged: enabled ? (value) => onChange(value) : null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('Scrollbar')),
|
||||
value: kRemoteScrollStyleBar,
|
||||
groupValue: groupValue,
|
||||
onChanged: enabled ? (value) => onChange(value) : null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
Divider(),
|
||||
]);
|
||||
return Obx(() => Column(children: [
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('ScrollAuto')),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
groupValue: groupValue,
|
||||
onChanged: widget.ffi.canvasModel.imageOverflow.value
|
||||
? (value) => onChange(value)
|
||||
: null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('Scrollbar')),
|
||||
value: kRemoteScrollStyleBar,
|
||||
groupValue: groupValue,
|
||||
onChanged: widget.ffi.canvasModel.imageOverflow.value
|
||||
? (value) => onChange(value)
|
||||
: null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
Divider(),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1245,6 +1309,296 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomScaleMenuControls extends StatefulWidget {
|
||||
final FFI ffi;
|
||||
final ValueChanged<int>? onChanged;
|
||||
const _CustomScaleMenuControls({Key? key, required this.ffi, this.onChanged}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_CustomScaleMenuControls> createState() => _CustomScaleMenuControlsState();
|
||||
}
|
||||
|
||||
class _CustomScaleMenuControlsState extends State<_CustomScaleMenuControls> {
|
||||
late int _value;
|
||||
late final Debouncer<int> _debouncerScale;
|
||||
// Normalized slider position in [0, 1]. We map it nonlinearly to percent.
|
||||
double _pos = 0.0;
|
||||
|
||||
// Piecewise mapping constants (moved to consts.dart)
|
||||
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 _clamp(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 _clamp(v.round());
|
||||
} else {
|
||||
final q = (p - _pivotPos) / (1.0 - _pivotPos); // 0..1
|
||||
final v = _pivotPercent + q * (_maxPercent - _pivotPercent);
|
||||
return _clamp(v.round());
|
||||
}
|
||||
}
|
||||
|
||||
// Map percent [5,1000] → normalized position [0,1]
|
||||
double _mapPercentToPos(int percent) {
|
||||
final p = _clamp(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();
|
||||
_value = 100;
|
||||
_debouncerScale = Debouncer<int>(
|
||||
kDebounceCustomScaleDuration,
|
||||
onChanged: (v) async {
|
||||
await _apply(v);
|
||||
},
|
||||
initialValue: _value,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
try {
|
||||
final v = await getSessionCustomScalePercent(widget.ffi.sessionId);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_value = v;
|
||||
_pos = _mapPercentToPos(v);
|
||||
});
|
||||
}
|
||||
} catch (e, st) {
|
||||
debugPrint('[CustomScale] Failed to get initial value: $e');
|
||||
debugPrintStack(stackTrace: st);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Future<void> _apply(int v) async {
|
||||
v = clampCustomScalePercent(v);
|
||||
setState(() {
|
||||
_value = v;
|
||||
});
|
||||
try {
|
||||
await bind.sessionSetFlutterOption(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
k: kCustomScalePercentKey,
|
||||
v: v.toString());
|
||||
final curStyle = await bind.sessionGetViewStyle(sessionId: widget.ffi.sessionId);
|
||||
if (curStyle != kRemoteViewStyleCustom) {
|
||||
await bind.sessionSetViewStyle(
|
||||
sessionId: widget.ffi.sessionId, value: kRemoteViewStyleCustom);
|
||||
}
|
||||
await widget.ffi.canvasModel.updateViewStyle();
|
||||
if (isMobile) {
|
||||
HapticFeedback.selectionClick();
|
||||
}
|
||||
widget.onChanged?.call(v);
|
||||
} catch (e, st) {
|
||||
debugPrint('[CustomScale] Apply failed: $e');
|
||||
debugPrintStack(stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
void _nudge(int delta) {
|
||||
final next = _clamp(_value + delta);
|
||||
setState(() {
|
||||
_value = next;
|
||||
_pos = _mapPercentToPos(next);
|
||||
});
|
||||
widget.onChanged?.call(next);
|
||||
_debouncerScale.value = next;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debouncerScale.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
const smallBtnConstraints = BoxConstraints(minWidth: 28, minHeight: 28);
|
||||
|
||||
final sliderControl = Semantics(
|
||||
label: translate('Custom scale slider'),
|
||||
value: '$_value%',
|
||||
child: SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: colorScheme.primary,
|
||||
thumbColor: colorScheme.primary,
|
||||
overlayColor: colorScheme.primary.withOpacity(0.1),
|
||||
showValueIndicator: ShowValueIndicator.never,
|
||||
thumbShape: _RectValueThumbShape(
|
||||
min: _minPercent.toDouble(),
|
||||
max: _maxPercent.toDouble(),
|
||||
width: 52,
|
||||
height: 24,
|
||||
radius: 4,
|
||||
// Display the mapped percent for the current normalized value
|
||||
displayValueForNormalized: (t) => _mapPosToPercent(t),
|
||||
),
|
||||
),
|
||||
child: Slider(
|
||||
value: _pos,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
// Use a wide range of divisions (calculated as (_maxPercent - _minPercent)) to provide ~1% precision increments.
|
||||
// This allows users to set precise scale values. Lower values would require more fine-tuning via the +/- buttons, which is undesirable for big ranges.
|
||||
divisions: (_maxPercent - _minPercent).round(),
|
||||
onChanged: (v) {
|
||||
final snapped = _snapNormalizedPos(v);
|
||||
final next = _mapPosToPercent(snapped);
|
||||
if (next != _value || snapped != _pos) {
|
||||
setState(() {
|
||||
_pos = snapped;
|
||||
_value = next;
|
||||
});
|
||||
widget.onChanged?.call(next);
|
||||
_debouncerScale.value = next;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(children: [
|
||||
Tooltip(
|
||||
message: translate('Decrease'),
|
||||
child: IconButton(
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(1),
|
||||
constraints: smallBtnConstraints,
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () => _nudge(-1),
|
||||
),
|
||||
),
|
||||
Expanded(child: sliderControl),
|
||||
Tooltip(
|
||||
message: translate('Increase'),
|
||||
child: IconButton(
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(1),
|
||||
constraints: smallBtnConstraints,
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => _nudge(1),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Divider(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Lightweight rectangular thumb that paints the current percentage.
|
||||
// Stateless and uses only SliderTheme colors; avoids allocations beyond a TextPainter per frame.
|
||||
class _RectValueThumbShape extends SliderComponentShape {
|
||||
final double min;
|
||||
final double max;
|
||||
final double width;
|
||||
final double height;
|
||||
final double radius;
|
||||
// Optional mapper to compute display value from normalized position [0,1]
|
||||
// If null, falls back to linear interpolation between min and max.
|
||||
final int Function(double normalized)? displayValueForNormalized;
|
||||
|
||||
const _RectValueThumbShape({
|
||||
required this.min,
|
||||
required this.max,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.radius,
|
||||
this.displayValueForNormalized,
|
||||
});
|
||||
|
||||
@override
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
||||
return Size(width, height);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required Animation<double> activationAnimation,
|
||||
required Animation<double> enableAnimation,
|
||||
required bool isDiscrete,
|
||||
required TextPainter labelPainter,
|
||||
required RenderBox parentBox,
|
||||
required SliderThemeData sliderTheme,
|
||||
required TextDirection textDirection,
|
||||
required double value,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
}) {
|
||||
final Canvas canvas = context.canvas;
|
||||
|
||||
// Resolve color based on enabled/disabled animation, with safe fallbacks.
|
||||
final ColorTween colorTween = ColorTween(
|
||||
begin: sliderTheme.disabledThumbColor,
|
||||
end: sliderTheme.thumbColor,
|
||||
);
|
||||
final Color? evaluatedColor = colorTween.evaluate(enableAnimation);
|
||||
final Color? thumbColor = sliderTheme.thumbColor;
|
||||
final Color fillColor = evaluatedColor ?? thumbColor ?? Colors.blueAccent;
|
||||
|
||||
final RRect rrect = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: center, width: width, height: height),
|
||||
Radius.circular(radius),
|
||||
);
|
||||
final Paint paint = Paint()..color = fillColor;
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// Compute displayed percent from normalized slider value.
|
||||
final int percent = displayValueForNormalized != null
|
||||
? displayValueForNormalized!(value)
|
||||
: (min + value * (max - min)).round();
|
||||
final TextSpan span = TextSpan(
|
||||
text: '$percent%',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
final TextPainter tp = TextPainter(
|
||||
text: span,
|
||||
textAlign: TextAlign.center,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
tp.layout(maxWidth: width - 4);
|
||||
tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
class _ResolutionsMenu extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
@@ -2266,6 +2620,8 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
final ValueChanged<T?>? onChanged;
|
||||
final Widget? child;
|
||||
final FFI? ffi;
|
||||
// When true, submenu will be dismissed on activate; when false, it stays open.
|
||||
final bool closeOnActivate;
|
||||
const RdoMenuButton({
|
||||
Key? key,
|
||||
required this.value,
|
||||
@@ -2273,6 +2629,7 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
required this.child,
|
||||
this.ffi,
|
||||
this.onChanged,
|
||||
this.closeOnActivate = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -2281,9 +2638,10 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
child: child,
|
||||
closeOnActivate: closeOnActivate,
|
||||
onChanged: onChanged != null
|
||||
? (T? value) {
|
||||
if (ffi != null) {
|
||||
if (ffi != null && closeOnActivate) {
|
||||
_menuDismissCallback(ffi!);
|
||||
}
|
||||
onChanged?.call(value);
|
||||
|
||||
@@ -42,6 +42,7 @@ import '../utils/image.dart' as img;
|
||||
import '../common/widgets/dialog.dart';
|
||||
import 'input_model.dart';
|
||||
import 'platform_model.dart';
|
||||
import 'package:flutter_hbb/utils/scale.dart';
|
||||
|
||||
import 'package:flutter_hbb/generated_bridge.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/bridge.dart';
|
||||
@@ -1699,6 +1700,8 @@ class ViewStyle {
|
||||
final s2 = height / displayHeight;
|
||||
s = s1 < s2 ? s1 : s2;
|
||||
}
|
||||
} else if (style == kRemoteViewStyleCustom) {
|
||||
// Custom scale is session-scoped and applied in CanvasModel.updateViewStyle()
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1815,7 +1818,13 @@ class CanvasModel with ChangeNotifier {
|
||||
displayWidth: displayWidth,
|
||||
displayHeight: displayHeight,
|
||||
);
|
||||
if (_lastViewStyle == viewStyle) {
|
||||
// If only the Custom scale percent changed, proceed to update even if
|
||||
// the basic ViewStyle fields are equal.
|
||||
// In Custom scale mode, the scale percent can change independently of the other
|
||||
// ViewStyle fields and is not captured by the equality check. Therefore, we must
|
||||
// allow updates to proceed when style == kRemoteViewStyleCustom, even if the
|
||||
// rest of the ViewStyle fields are unchanged.
|
||||
if (_lastViewStyle == viewStyle && style != kRemoteViewStyleCustom) {
|
||||
return;
|
||||
}
|
||||
if (_lastViewStyle.style != viewStyle.style) {
|
||||
@@ -1824,12 +1833,26 @@ class CanvasModel with ChangeNotifier {
|
||||
_lastViewStyle = viewStyle;
|
||||
_scale = viewStyle.scale;
|
||||
|
||||
// Apply custom scale percent when in Custom mode
|
||||
if (style == kRemoteViewStyleCustom) {
|
||||
try {
|
||||
_scale = await getSessionCustomScale(sessionId);
|
||||
} catch (e, stack) {
|
||||
debugPrint('Error in getSessionCustomScale: $e');
|
||||
debugPrintStack(stackTrace: stack);
|
||||
_scale = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
_devicePixelRatio = ui.window.devicePixelRatio;
|
||||
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
|
||||
_scale = 1.0 / _devicePixelRatio;
|
||||
}
|
||||
_resetCanvasOffset(displayWidth, displayHeight);
|
||||
_imageOverflow.value = _x < 0 || y < 0;
|
||||
final overflow = _x < 0 || y < 0;
|
||||
if (_imageOverflow.value != overflow) {
|
||||
_imageOverflow.value = overflow;
|
||||
}
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -1850,7 +1873,7 @@ class CanvasModel with ChangeNotifier {
|
||||
tryUpdateScrollStyle(Duration duration, String? style) async {
|
||||
if (_scrollStyle != ScrollStyle.scrollbar) return;
|
||||
style ??= await bind.sessionGetViewStyle(sessionId: sessionId);
|
||||
if (style != kRemoteViewStyleOriginal) {
|
||||
if (style != kRemoteViewStyleOriginal && style != kRemoteViewStyleCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
34
flutter/lib/utils/scale.dart
Normal file
34
flutter/lib/utils/scale.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Clamp custom scale percent to supported bounds.
|
||||
/// Keep this in sync with the slider's minimum in the desktop toolbar UI.
|
||||
///
|
||||
/// This function exists to ensure consistent clamping behavior across the app
|
||||
/// and to provide a single point of reference for the valid scale range.
|
||||
int clampCustomScalePercent(int percent) {
|
||||
return percent.clamp(kScaleCustomMinPercent, kScaleCustomMaxPercent);
|
||||
}
|
||||
|
||||
/// Parse a string percent and clamp. Defaults to 100 when invalid.
|
||||
int parseCustomScalePercent(String? s, {int defaultPercent = 100}) {
|
||||
final parsed = int.tryParse(s ?? '') ?? defaultPercent;
|
||||
return clampCustomScalePercent(parsed);
|
||||
}
|
||||
|
||||
/// Convert a percent value to scale factor after clamping.
|
||||
double percentToScale(int percent) => clampCustomScalePercent(percent) / 100.0;
|
||||
|
||||
/// Fetch, parse and clamp the custom scale percent for a session.
|
||||
Future<int> getSessionCustomScalePercent(UuidValue sessionId) async {
|
||||
final opt = await bind.sessionGetFlutterOption(
|
||||
sessionId: sessionId, k: kCustomScalePercentKey);
|
||||
return parseCustomScalePercent(opt);
|
||||
}
|
||||
|
||||
/// Fetch and compute the custom scale factor for a session.
|
||||
Future<double> getSessionCustomScale(UuidValue sessionId) async {
|
||||
final p = await getSessionCustomScalePercent(sessionId);
|
||||
return percentToScale(p);
|
||||
}
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "يرجى إدخال اسم مستخدم بصلاحيات المسؤول للمتابعة."),
|
||||
("Preparing for installation ...", "جارٍ التحضير للتثبيت..."),
|
||||
("Show my cursor", "إظهار المؤشر الخاص بي"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", "Escala personalitzada"),
|
||||
("Custom scale slider", "Control lliscant d'escala personalitzada"),
|
||||
("Decrease", "Disminueix"),
|
||||
("Increase", "Augmenta"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "输入用户名或域名\\用户名"),
|
||||
("Preparing for installation ...", "准备安装..."),
|
||||
("Show my cursor", "显示我的光标"),
|
||||
("Scale custom", "自定义缩放"),
|
||||
("Custom scale slider", "自定义缩放滑块"),
|
||||
("Decrease", "缩小"),
|
||||
("Increase", "放大"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Geben Sie Benutzername oder Domäne\\Benutzername ein"),
|
||||
("Preparing for installation ...", "Installation wird vorbereitet …"),
|
||||
("Show my cursor", "Meinen Cursor anzeigen"),
|
||||
("Scale custom", "Benutzerdefinierte Skalierung"),
|
||||
("Custom scale slider", "Schieberegler für benutzerdefinierte Skalierung"),
|
||||
("Decrease", "Verringern"),
|
||||
("Increase", "Erhöhen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Introduzca el nombre de usuario o dominio\\NombreDeUsuario"),
|
||||
("Preparing for installation ...", "Preparando la instalación ..."),
|
||||
("Show my cursor", "Mostrar mi cursor"),
|
||||
("Scale custom", "Escala personalizada"),
|
||||
("Custom scale slider", "Control deslizante de escala personalizada"),
|
||||
("Decrease", "Disminuir"),
|
||||
("Increase", "Aumentar"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "لطفاً نام کاربری مدیریتی را برای ارتقاء دسترسی وارد کنید."),
|
||||
("Preparing for installation ...", "در حال آمادهسازی برای نصب..."),
|
||||
("Show my cursor", "نمایش نشانگر من"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Saisissez un nom d’utilisateur ou un domaine\\utilisateur"),
|
||||
("Preparing for installation ...", "Préparation de l’installation…"),
|
||||
("Show my cursor", "Afficher mon curseur"),
|
||||
("Scale custom", "Mise à l’échelle personnalisée"),
|
||||
("Custom scale slider", "Curseur d’échelle personnalisée"),
|
||||
("Decrease", "Diminuer"),
|
||||
("Increase", "Augmenter"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "רמז_ליוזר_להעלאת_הרשאה"),
|
||||
("Preparing for installation ...", "הכנה להתקנה..."),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -711,5 +711,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Felhasználónév vagy tartománynév megadása\\felhasználónév"),
|
||||
("Preparing for installation ...", "Felkészülés a telepítésre ..."),
|
||||
("Show my cursor", "Kurzor megjelenítése"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "panduan_elevasi_nama_pengguna"),
|
||||
("Preparing for installation ...", "Mempersiapkan instalasi ..."),
|
||||
("Show my cursor", "Tampilkan kursor saya"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Inserisci Nome utente o dominio sorgente\\nome Utente"),
|
||||
("Preparing for installation ...", "Preparazione per l'installazione..."),
|
||||
("Show my cursor", "Visualizza il mio cursore"),
|
||||
("Scale custom", "Scala personalizzata"),
|
||||
("Custom scale slider", "Cursore scala personalizzata"),
|
||||
("Decrease", "Diminuisci"),
|
||||
("Increase", "Aumenta"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -709,6 +709,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Supported only in the installed version.", "インストールされたバージョンでのみサポートされます。"),
|
||||
("elevation_username_tip", "ユーザー名またはドメインのユーザー名を入力してください。"),
|
||||
("Preparing for installation ...", "インストールの準備中です..."),
|
||||
("Show my cursor", ""),
|
||||
("Show my cursor", "自分のカーソルを表示"),
|
||||
("Scale custom", "カスタムスケーリング"),
|
||||
("Custom scale slider", "カスタムスケールのスライダー"),
|
||||
("Decrease", "縮小"),
|
||||
("Increase", "拡大"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "사용자 이름 또는 도메인\\사용자 이름 입력"),
|
||||
("Preparing for installation ...", "설치 준비 중 ..."),
|
||||
("Show my cursor", "내 커서 표시"),
|
||||
("Scale custom", "사용자 지정 크기 조정"),
|
||||
("Custom scale slider", "사용자 지정 크기 조정 슬라이더"),
|
||||
("Decrease", "축소"),
|
||||
("Increase", "확대"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Ievadiet lietotājvārdu vai domēnu\\lietotājvārdu"),
|
||||
("Preparing for installation ...", "Gatavošanās instalēšanai..."),
|
||||
("Show my cursor", "Rādīt manu kursoru"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Voer je gebruikersnaam of domeinnaam in"),
|
||||
("Preparing for installation ...", "Installatie voorbereiden ..."),
|
||||
("Show my cursor", "Toon mijn cursor"),
|
||||
("Scale custom", "Aangepaste schaal"),
|
||||
("Custom scale slider", "Aangepaste schuifregelaar voor schaal"),
|
||||
("Decrease", "Verlagen"),
|
||||
("Increase", "Verhogen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Podaj nazwę użytkownika lub domena\\użytkownik"),
|
||||
("Preparing for installation ...", "Przygotowywanie do instalacji ..."),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", "Escala personalizada"),
|
||||
("Custom scale slider", "Controlo deslizante de escala personalizada"),
|
||||
("Decrease", "Diminuir"),
|
||||
("Increase", "Aumentar"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", "Escala personalizada"),
|
||||
("Custom scale slider", "Controle deslizante de escala personalizada"),
|
||||
("Decrease", "Diminuir"),
|
||||
("Increase", "Aumentar"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", "Scalare personalizată"),
|
||||
("Custom scale slider", "Glisor pentru scalare personalizată"),
|
||||
("Decrease", "Micșorează"),
|
||||
("Increase", "Mărește"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Введите пользователя или домен\\пользователя"),
|
||||
("Preparing for installation ...", "Подготовка к установке..."),
|
||||
("Show my cursor", "Показывать мой курсор"),
|
||||
("Scale custom", "Пользовательский масштаб"),
|
||||
("Custom scale slider", "Ползунок пользовательского масштаба"),
|
||||
("Decrease", "Уменьшить"),
|
||||
("Increase", "Увеличить"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Inserta Nùmene utente o domìniu de fonte\\nùmene Utente"),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -709,6 +709,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Supported only in the installed version.", "Stöds endast i den installerade versionen."),
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", "Förbereder för installation ..."),
|
||||
("Show my cursor", "Via min muspekare"),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "輸入使用者名稱或網域\\使用者名稱"),
|
||||
("Preparing for installation ...", "正在準備安裝..."),
|
||||
("Show my cursor", "顯示我的游標"),
|
||||
("Scale custom", "自訂縮放"),
|
||||
("Custom scale slider", "自訂縮放滑桿"),
|
||||
("Decrease", "縮小"),
|
||||
("Increase", "放大"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", "Користувацький масштаб"),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user