From 4f5d4f802efa39279818ebf9b1e2475cc228b6e8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 29 Mar 2025 13:04:32 +0100 Subject: [PATCH] sideleft: new tool: conversions --- .config/ags/modules/.miscutils/mathfuncs.js | 6 + .config/ags/modules/sideleft/toolbox.js | 2 + .../ags/modules/sideleft/tools/conversions.js | 169 ++++++++++++++++++ .config/ags/scss/_lib_mixins.scss | 5 + .config/ags/scss/_sidebars.scss | 25 +++ 5 files changed, 207 insertions(+) create mode 100644 .config/ags/modules/sideleft/tools/conversions.js diff --git a/.config/ags/modules/.miscutils/mathfuncs.js b/.config/ags/modules/.miscutils/mathfuncs.js index ba1c0b594..966eede64 100644 --- a/.config/ags/modules/.miscutils/mathfuncs.js +++ b/.config/ags/modules/.miscutils/mathfuncs.js @@ -1,4 +1,10 @@ export function clamp(x, min, max) { return Math.min(Math.max(x, min), max); +} + +export function truncateToPrecision(value, precision) { + const factor = Math.pow(10, precision); + const result = Math.round(value * factor) / factor; + return result; } \ No newline at end of file diff --git a/.config/ags/modules/sideleft/toolbox.js b/.config/ags/modules/sideleft/toolbox.js index a21d195d6..879082fde 100644 --- a/.config/ags/modules/sideleft/toolbox.js +++ b/.config/ags/modules/sideleft/toolbox.js @@ -2,6 +2,7 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; const { Box, Label, Scrollable } = Widget; import QuickScripts from './tools/quickscripts.js'; import ColorPicker from './tools/colorpicker.js'; +import Conversions from './tools/conversions.js'; import Name from './tools/name.js'; export default Scrollable({ @@ -12,6 +13,7 @@ export default Scrollable({ className: 'spacing-v-10', children: [ QuickScripts(), + Conversions(), ColorPicker(), Box({ vexpand: true }), Name(), diff --git a/.config/ags/modules/sideleft/tools/conversions.js b/.config/ags/modules/sideleft/tools/conversions.js new file mode 100644 index 000000000..bc3180dec --- /dev/null +++ b/.config/ags/modules/sideleft/tools/conversions.js @@ -0,0 +1,169 @@ +const { Gtk } = imports.gi; +import App from 'resource:///com/github/Aylur/ags/app.js'; +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; +const { execAsync, exec } = Utils; +const { Box, Button, Entry, EventBox, Icon, Label, Scrollable, Overlay } = Widget; +import SidebarModule from './module.js'; +import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; +import { setupCursorHover } from '../../.widgetutils/cursorhover.js'; +import { truncateToPrecision } from '../../.miscutils/mathfuncs.js'; + +const VALUE_DEFAULT_PRECISION = 3; +const conversions = [ + { + unit1: 'px', + unit2: 'rem', + unit1Default: 5, + formula1to2: '{{x}} / (parseFloat(Utils.exec(\'gsettings get org.gnome.desktop.interface font-name\').split(" ")[1].split("\'"))*4/3)', + formula2to1: '{{x}} * 3 / 4', + forcePrecision: true, + }, + { + unit1: 'degrees', + unit2: 'radians', + unit1Default: 90, + formula1to2: '{{x}} * Math.PI / 180', + formula2to1: '{{x}} * 180 / Math.PI', + }, + { + unit1: '°F', + unit2: '°C', + unit1Default: 68, + formula1to2: '({{x}} - 32) * 5 / 9', + formula2to1: '{{x}} * 9 / 5 + 32', + }, + { + unit1: 'Ft', + unit2: 'Cm', + formula1to2: '{{x}} * 30.48', + formula2to1: '{{x}} / 30.48', + }, + // { + // unit1: 'Mile', + // unit2: 'Km', + // formula1to2: '{{x}} * 1.60934', + // formula2to1: '{{x}} / 1.60934', + // }, + // { + // unit1: 'Inch', + // unit2: 'Cm', + // formula1to2: '{{x}} * 2.54', + // formula2to1: '{{x}} / 2.54', + // }, + { + unit1: 'lbs', + unit2: 'Kg', + formula1to2: '{{x}} * 0.453592', + formula2to1: '{{x}} / 0.453592', + } +] + +export default () => { + const ValueBox = ({ unit, initValue = 0, updateCallback }) => { + const unitName = Label({ + xalign: 0, + className: 'txt txt-smallie', + label: `${unit}`, + }); + const entry = Entry({ + hexpand: 'true', + widthChars: 10, + className: 'txt-small techfont', + text: `${initValue}`, + onChange: updateCallback, + }); + const copyButton = Button({ + className: 'sidebar-module-csscalc-valuebox-copybtn', + child: MaterialIcon('content_copy', 'norm'), + onClicked: (self) => { + Utils.execAsync(['wl-copy', entry.text]); + self.child.label = 'done'; + Utils.timeout(1000, () => self.child.label = 'content_copy'); + }, + setup: setupCursorHover, + }); + const wholeThing = Box({ + className: 'sidebar-module-csscalc-valuebox', + vertical: true, + hexpand: true, + children: [ + unitName, + Box({ + children: [ + entry, + copyButton, + ] + }) + ], + attribute: { + updateValue: (value) => entry.text = `${value}`, + getValue: () => entry.text, + } + }); + return wholeThing; + } + // Formula format is js expression, with `{{x}}` being the input value + const BidirectionalConversion = ({ + unit1, unit2, unit1Default = 1, + formula1to2, formula2to1, + forcePrecision = false, precision = VALUE_DEFAULT_PRECISION, + }) => { + let updateLock = false; + const convert = (value, formula) => { + let thisValue; + try { + thisValue = eval(value) + } catch (error) { + thisValue = parseFloat(value); + } + // print(formula.replace('{{x}}', thisValue)) + // print(eval(formula.replace('{{x}}', thisValue))) + const evalResult = eval(formula.replace('{{x}}', thisValue)); + const result = forcePrecision ? + evalResult.toFixed(precision) : truncateToPrecision(evalResult, precision); + // print(result) + return result; + } + const unit1Box = ValueBox({ + unit: unit1, + initValue: unit1Default, + updateCallback: (self) => { + if (updateLock) return; + updateLock = true; + const newValue = convert(self.text, formula1to2); + unit2Box.attribute.updateValue(newValue || 0); + updateLock = false; + }, + }); + const unit2Box = ValueBox({ + unit: unit2, + initValue: truncateToPrecision(eval(formula1to2.replace('\{{x}}', unit1Default)), precision), + updateCallback: (self) => { + if (updateLock) return; + updateLock = true; + const newValue = convert(self.text, formula2to1); + unit1Box.attribute.updateValue(newValue || 0); + updateLock = false; + }, + }); + return Box({ + className: 'txt spacing-h-10', + children: [ + unit1Box, + MaterialIcon('swap_horiz', 'large'), + unit2Box, + ] + }) + } + + return SidebarModule({ + icon: MaterialIcon('autorenew', 'norm'), + name: getString('Conversions'), + child: Box({ + vertical: true, + className: 'spacing-v-5', + children: conversions.map(BidirectionalConversion), + }) + }); +} \ No newline at end of file diff --git a/.config/ags/scss/_lib_mixins.scss b/.config/ags/scss/_lib_mixins.scss index 36a458bc1..104e32019 100644 --- a/.config/ags/scss/_lib_mixins.scss +++ b/.config/ags/scss/_lib_mixins.scss @@ -14,6 +14,11 @@ $rounding_large: 1.705rem; border-radius: $rounding_unsharpen; } +@mixin verysmall-rounding { + border-radius: $rounding_verysmall; + -gtk-outline-radius: $rounding_verysmall; +} + @mixin small-rounding { border-radius: $rounding_small; -gtk-outline-radius: $rounding_small; diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index 52103c462..19bdd9d41 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -510,6 +510,31 @@ $colorpicker_rounding: 0.341rem; padding: 0.341rem; } +.sidebar-module-csscalc-valuebox { + @include small-rounding; + padding: 0.341rem; + background-color: $layer2; + color: $onLayer2; +} + +.sidebar-module-csscalc-valuebox-copybtn { + @include verysmall-rounding; + @include element_decel; + min-width: 1.705rem; + min-height: 1.705rem; + background-color: $layer2; + color: $onLayer2; + + &:hover, + &:focus { + background-color: $layer2Hover; + } + + &:active { + background-color: $layer2Active; + } +} + .sidebar-icontabswitcher { @include full-rounding; @include group-padding;