diff --git a/main.ts b/main.ts index f4e73a1..d85da23 100644 --- a/main.ts +++ b/main.ts @@ -3,6 +3,7 @@ import "scss/style" import { Bar } from "modules/bar/Bar" import MenuWindows from "./modules/menus/main.js"; +import SettingsDialog from "widget/settings/SettingsDialog" import Notifications from "./modules/notifications/index.js"; import { forMonitors } from "lib/utils" @@ -11,6 +12,7 @@ App.config({ windows: [ ...MenuWindows, Notifications(), + SettingsDialog(), ...forMonitors(Bar), ], closeWindowDelay: { diff --git a/modules/menus/dashboard/shortcuts/index.js b/modules/menus/dashboard/shortcuts/index.js index 90d3720..8f76952 100644 --- a/modules/menus/dashboard/shortcuts/index.js +++ b/modules/menus/dashboard/shortcuts/index.js @@ -18,6 +18,7 @@ const Shortcuts = () => { }); const handleClick = (action, resolver, tOut = 250) => { App.closeWindow("dashboardmenu"); + App.toggleWindow("settings-dialog"); setTimeout(() => { Utils.execAsync(action) diff --git a/modules/shared/barItemBox.js b/modules/shared/barItemBox.js index 65901e8..caebb74 100644 --- a/modules/shared/barItemBox.js +++ b/modules/shared/barItemBox.js @@ -7,7 +7,7 @@ export const BarItemBox = (child) => { }; return Widget.Button({ - class_name: "bar_item_box_visible", + class_name: `bar_item_box_visible ${Object.hasOwnProperty.call(child, "boxClass") ? child.boxClass : ""}`, child: child.component, visible: computeVisible(), ...child.props, diff --git a/options.ts b/options.ts index 1e9ab11..345b16b 100644 --- a/options.ts +++ b/options.ts @@ -31,7 +31,6 @@ const colors = { }; const options = mkOptions(OPTIONS, { - autotheme: opt(false), theme: { font: { size: opt("1.2rem"), @@ -59,6 +58,7 @@ const options = mkOptions(OPTIONS, { background: opt(colors.crust), buttons: { monochrome: opt(false), + radius: opt("0.3em"), background: opt(colors.base2), hover: opt(colors.surface1), text: opt(colors.lavender), @@ -109,11 +109,6 @@ const options = mkOptions(OPTIONS, { background: opt(colors.base2), hover: opt(colors.surface1), }, - power: { - background: opt(colors.base2), - hover: opt(colors.surface1), - icon: opt(colors.red), - }, battery: { background: opt(colors.base2), hover: opt(colors.surface1), @@ -139,7 +134,7 @@ const options = mkOptions(OPTIONS, { cards: opt(colors.base), card_radius: opt("0.4em"), border: { - size: opt("0.13 em"), + size: opt("0.13em"), radius: opt("0.7em"), color: opt(colors.surface0) }, @@ -561,7 +556,7 @@ const options = mkOptions(OPTIONS, { active: opt(""), occupied: opt(""), }, - workspaces: opt(7), + workspaces: opt(10), monitorSpecific: opt(true), }, volume: { diff --git a/scss/entry.scss b/scss/entry.scss index fdaf0aa..ca895e1 100644 --- a/scss/entry.scss +++ b/scss/entry.scss @@ -41,3 +41,6 @@ //notifications @import "style/notifications/popups"; + +//settings dialog +@import "style/settings/dialog" diff --git a/scss/main.scss b/scss/main.scss index 2a65b84..bd5aef5 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -40,3 +40,6 @@ //notifications @import "style/notifications/popups"; + +//settings dialog +@import "style/settings/dialog" diff --git a/scss/style/bar/bar.scss b/scss/style/bar/bar.scss index bcf5a01..8ce5534 100644 --- a/scss/style/bar/bar.scss +++ b/scss/style/bar/bar.scss @@ -10,7 +10,7 @@ .bar_item_box_visible { background-color: $bar-buttons-background; - border-radius: 0.35em; + border-radius: $bar-buttons-radius; padding: 0.2rem 0.9rem; margin: 0.5rem 0.25rem; @@ -60,12 +60,6 @@ background: if($bar-buttons-monochrome, $bar-buttons-hover, $bar-buttons-notifications-background); } } - &.power { - background-color: if($bar-buttons-monochrome, $bar-buttons-background, $bar-buttons-power-background); - &:hover { - background: if($bar-buttons-monochrome, $bar-buttons-hover, $bar-buttons-power-background); - } - } &.systray { background-color: if($bar-buttons-monochrome, $bar-buttons-background, $bar-buttons-systray-background); &:hover { diff --git a/scss/style/bar/power.scss b/scss/style/bar/power.scss index 7d2cd2f..bab08a2 100644 --- a/scss/style/bar/power.scss +++ b/scss/style/bar/power.scss @@ -2,6 +2,6 @@ @import '../../variables'; .bar-power_label { - color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-power-icon); + color: $red; margin-top: 0.2rem; } diff --git a/scss/style/settings/dialog.scss b/scss/style/settings/dialog.scss new file mode 100644 index 0000000..f3f2d6d --- /dev/null +++ b/scss/style/settings/dialog.scss @@ -0,0 +1,172 @@ +@import '../../variables'; + +window.settings-dialog { + background-color: $bar-menus-cards; + color: $bar-menus-text; + + $padding: 1em; + $primary_bg: $bar-menus-background; + $spacing: 0.4em; + $radius: 0.5em; + $widget-bg: $bar-menus-cards; + $border: none; + $fg: $bar-menus-text; + + + .header { + + padding: $padding; + + button { + font-weight: bold; + padding: $padding*.5 $padding; + + } + + button.close { + padding: $padding * .5; + } + + button.reset { + padding: $padding*.5; + } + } + + .page { + + .page-content { + padding: $padding*2; + padding-top: 0; + } + } + + .group { + .group-title { + color: $primary-bg; + margin-bottom: $spacing*.5; + } + + .group-reset { + margin: $spacing * .5; + padding: $padding * .5; + + &:disabled { + color: transparent; + } + } + + &:not(:first-child) { + margin-top: $spacing; + } + } + + .row { + background-color: $widget-bg; + padding: $padding; + border: $border; + border-top: none; + + &:first-child { + border-radius: $radius $radius 0em 0em; + border: $border; + } + + &:last-child { + border-radius: 0em 0em $radius $radius; + } + + &:first-child:last-child { + border-radius: $radius; + border: $border; + } + + button.reset { + margin-left: $spacing; + } + + label.id, + label.note { + color: transparentize($fg, .4) + } + + entry, + button { + padding: $padding; + } + + spinbutton { + entry { + border-radius: $radius 0em 0em $radius; + } + + button { + border-radius: 0em; + } + + button:last-child { + border-radius: 0em $radius $radius 0em; + } + } + + .enum-setter { + label { + background-color: $widget-bg; + border: $border; + padding: 0em $padding; + border-radius: $radius 0em 0em $radius; + } + + button { + border-radius: 0em; + } + + button:last-child { + border-radius: 0em $radius $radius 0em; + } + } + + &.wallpaper { + button { + margin-top: $spacing * .5; + } + + .preview { + border-radius: $radius; + } + } + } +} + +.option-item { + margin: 0em 2em; + margin-bottom: 1em; + + .reset { + color: $bar-menus-iconbuttons-passive; + } + .options-label { + color: $bar-menus-text; + } + .options-sublabel { + font-size: 0.75em; + margin-top: 0.2em; + color: $bar-menus-dimtext; + } + .inputter-container { + border-radius: $bar-menus-border-radius * 0.5; + :first-child { + border-radius: $bar-menus-border-radius * 0.5; + } + padding: 0.35em 0.35em; + background: $surface1; + margin-right: 1em; + } +} +.options-header { + margin: 1em 1em; + .label-name { + color: $bar-menus-label; + font-size: 0.9em; + margin-right: 0.5em; + } +} diff --git a/scss/variables.scss b/scss/variables.scss index 873f9bb..e5edb44 100644 --- a/scss/variables.scss +++ b/scss/variables.scss @@ -14,6 +14,7 @@ $notification-close_button-label: #11111b; $bar-transparent: false; $bar-background: #11111b; $bar-buttons-monochrome: false; +$bar-buttons-radius: 0.3em; $bar-buttons-background: #242438; $bar-buttons-hover: #45475a; $bar-buttons-text: #b4befe; @@ -48,9 +49,6 @@ $bar-buttons-bluetooth-text: #89dceb; $bar-buttons-bluetooth-icon: #89dceb; $bar-buttons-systray-background: #242438; $bar-buttons-systray-hover: #45475a; -$bar-buttons-power-background: #242438; -$bar-buttons-power-hover: #45475a; -$bar-buttons-power-icon: #f38ba8; $bar-buttons-battery-background: #242438; $bar-buttons-battery-hover: #45475a; $bar-buttons-battery-text: #f9e2af; @@ -68,7 +66,7 @@ $bar-menus-background: #11111b; $bar-menus-cards: #1e1e2e; $bar-menus-card_radius: 0.4em; $bar-menus-border-size: 0.13em; -$bar-menus-border-radius: 0.4em; +$bar-menus-border-radius: 0.7em; $bar-menus-border-color: #313244; $bar-menus-text: #cdd6f4; $bar-menus-dimtext: #585b70; diff --git a/widget/RegularWindow.ts b/widget/RegularWindow.ts new file mode 100644 index 0000000..1e4225d --- /dev/null +++ b/widget/RegularWindow.ts @@ -0,0 +1,3 @@ +import Gtk from "gi://Gtk?version=3.0" + +export default Widget.subclass(Gtk.Window) diff --git a/widget/settings/SettingsDialog.ts b/widget/settings/SettingsDialog.ts new file mode 100644 index 0000000..fd019ff --- /dev/null +++ b/widget/settings/SettingsDialog.ts @@ -0,0 +1,45 @@ +import RegularWindow from "widget/RegularWindow" +import icons from "lib/icons" +import options from "options" +import { BarTheme } from "./pages/theme/bar/index" +const Header = () => Widget.CenterBox({ + class_name: "header", + start_widget: Widget.Button({ + class_name: "reset", + on_clicked: options.reset, + hpack: "start", + vpack: "start", + child: Widget.Icon(icons.ui.refresh), + tooltip_text: "Reset", + }), + center_widget: Widget.Box({ + + }), + end_widget: Widget.Button({ + class_name: "close", + hpack: "end", + vpack: "start", + child: Widget.Icon(icons.ui.close), + on_clicked: () => App.closeWindow("settings-dialog"), + }), +}) + +export default () => RegularWindow({ + name: "settings-dialog", + class_name: "settings-dialog", + title: "Settings", + setup(win) { + win.on("delete-event", () => { + win.hide() + return true + }) + win.set_default_size(500, 600) + }, + child: Widget.Box({ + vertical: true, + children: [ + Header(), + BarTheme() + ], + }), +}) diff --git a/widget/settings/pages/config/general/index.ts b/widget/settings/pages/config/general/index.ts new file mode 100644 index 0000000..ea42167 --- /dev/null +++ b/widget/settings/pages/config/general/index.ts @@ -0,0 +1,17 @@ +import { Option } from "widget/settings/shared/Option"; +import { Header } from "widget/settings/shared/Header"; + +import options from "options"; + +export const BarGeneral = () => { + return Widget.Box({ + class_name: "bar-theme-page", + vertical: true, + children: [ + Header('General Settings'), + Option({ opt: options.theme.font.name, title: 'Font', type: 'string' }), + Option({ opt: options.theme.font.size, title: 'Font Size', type: 'string' }), + Option({ opt: options.theme.font.weight, title: 'Font Weight', subtitle: "100, 200, 300, etc.", type: 'number' }), + ] + }) +} diff --git a/widget/settings/pages/theme/bar/index.ts b/widget/settings/pages/theme/bar/index.ts new file mode 100644 index 0000000..34aa156 --- /dev/null +++ b/widget/settings/pages/theme/bar/index.ts @@ -0,0 +1,91 @@ +import { Option } from "widget/settings/shared/Option"; +import { Header } from "widget/settings/shared/Header"; + +import options from "options"; + +export const BarTheme = () => { + return Widget.Scrollable({ + vscroll: "automatic", + hscroll: "never", + class_name: "bar-theme-page", + vexpand: true, + child: Widget.Box({ + vertical: true, + children: [ + Header('General'), + Option({ opt: options.theme.bar.transparent, title: 'Transparent', type: 'boolean' }), + Option({ opt: options.theme.bar.background, title: 'Background Color', type: 'color' }), + Option({ opt: options.theme.bar.buttons.monochrome, title: 'Use Global Colors', type: 'boolean' }), + Option({ opt: options.theme.bar.buttons.radius, title: 'Button Radius', type: 'string' }), + Option({ opt: options.theme.bar.buttons.background, title: 'Button Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.hover, title: 'Button Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.text, title: 'Button Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.icon, title: 'Button Icon', type: 'color' }), + + Header('Dashboard Button'), + Option({ opt: options.theme.bar.buttons.dashboard.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.dashboard.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.dashboard.icon, title: 'Icon', type: 'color' }), + + Header('Workspaces'), + Option({ opt: options.theme.bar.buttons.workspaces.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.workspaces.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.workspaces.available, title: 'Workspace Available Color', type: 'color' }), + Option({ opt: options.theme.bar.buttons.workspaces.occupied, title: 'Workspace Occupied Color', type: 'color' }), + Option({ opt: options.theme.bar.buttons.workspaces.active, title: 'Workspace Active Color', type: 'color' }), + + Header('Window Title'), + Option({ opt: options.theme.bar.buttons.windowtitle.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.windowtitle.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.windowtitle.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.windowtitle.icon, title: 'Icon', type: 'color' }), + + Header('Media'), + Option({ opt: options.theme.bar.buttons.media.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.media.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.media.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.media.icon, title: 'Icon', type: 'color' }), + + Header('Volume'), + Option({ opt: options.theme.bar.buttons.volume.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.volume.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.volume.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.volume.icon, title: 'Icon', type: 'color' }), + + Header('Network'), + Option({ opt: options.theme.bar.buttons.network.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.network.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.network.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.network.icon, title: 'Icon', type: 'color' }), + + Header('Bluetooth'), + Option({ opt: options.theme.bar.buttons.bluetooth.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.bluetooth.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.bluetooth.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.bluetooth.icon, title: 'Icon', type: 'color' }), + + Header('System Tray'), + Option({ opt: options.theme.bar.buttons.systray.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.systray.hover, title: 'Hover', type: 'color' }), + + Header('Battery'), + Option({ opt: options.theme.bar.buttons.battery.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.battery.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.battery.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.battery.icon, title: 'Icon', type: 'color' }), + + Header('Clock'), + Option({ opt: options.theme.bar.buttons.clock.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.clock.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.clock.text, title: 'Text', type: 'color' }), + Option({ opt: options.theme.bar.buttons.clock.icon, title: 'Icon', type: 'color' }), + + Header('Notifications'), + Option({ opt: options.theme.bar.buttons.notifications.background, title: 'Background', type: 'color' }), + Option({ opt: options.theme.bar.buttons.notifications.hover, title: 'Hover', type: 'color' }), + Option({ opt: options.theme.bar.buttons.notifications.total, title: 'Notification Count', type: 'color' }), + Option({ opt: options.theme.bar.buttons.notifications.icon, title: 'Icon', type: 'color' }), + ] + }) + }) +} diff --git a/widget/settings/pages/theme/menus/index.ts b/widget/settings/pages/theme/menus/index.ts new file mode 100644 index 0000000..0d394ef --- /dev/null +++ b/widget/settings/pages/theme/menus/index.ts @@ -0,0 +1,32 @@ +import { Option } from "widget/settings/shared/Option"; +import { Header } from "widget/settings/shared/Header"; + +import options from "options"; + +export const MenuTheme = () => { + return Widget.Scrollable({ + vscroll: "automatic", + hscroll: "never", + class_name: "menu-theme-page", + vexpand: true, + child: Widget.Box({ + vertical: true, + children: [ + Header('General'), + Option({ opt: options.theme.bar.menus.monochrome, title: 'Use Global Colors', type: 'boolean' }), + Option({ opt: options.theme.bar.menus.background, title: 'Background Color', type: 'color' }), + Option({ opt: options.theme.bar.menus.cards, title: 'Cards', type: 'color' }), + Option({ opt: options.theme.bar.menus.card_radius, title: 'Card Radius', type: 'string' }), + Option({ opt: options.theme.bar.menus.text, title: 'Primary Text', type: 'color' }), + Option({ opt: options.theme.bar.menus.dimtext, title: 'Dim Text', type: 'color' }), + Option({ opt: options.theme.bar.menus.feinttext, title: 'Feint Text', type: 'color' }), + Option({ opt: options.theme.bar.menus.label, title: 'Label Color', type: 'color' }), + + Header('Border'), + Option({ opt: options.theme.bar.menus.border.size, title: 'Border Width', type: 'string' }), + Option({ opt: options.theme.bar.menus.border.radius, title: 'Border Radius', type: 'string' }), + Option({ opt: options.theme.bar.menus.border.color, title: 'Border Color', type: 'color' }), + ] + }) + }) +} diff --git a/widget/settings/pages/theme/notifications/index.ts b/widget/settings/pages/theme/notifications/index.ts new file mode 100644 index 0000000..ea880ce --- /dev/null +++ b/widget/settings/pages/theme/notifications/index.ts @@ -0,0 +1,29 @@ +import { Option } from "widget/settings/shared/Option"; +import { Header } from "widget/settings/shared/Header"; + +import options from "options"; + +export const NotificationsTheme = () => { + return Widget.Scrollable({ + vscroll: "automatic", + hscroll: "never", + class_name: "notifications-theme-page", + vexpand: true, + child: Widget.Box({ + vertical: true, + children: [ + Header('Notifications Theme Settings'), + Option({ opt: options.theme.notification.background, title: 'Notification Background', type: 'color' }), + Option({ opt: options.theme.notification.actions.background, title: 'Action Button Background', subtitle: 'Buttons that perform actions within a notification', type: 'color' }), + Option({ opt: options.theme.notification.actions.text, title: 'Action Button Text Color', type: 'color' }), + Option({ opt: options.theme.notification.label, title: 'Label', type: 'color' }), + Option({ opt: options.theme.notification.border, title: 'Border', type: 'color' }), + Option({ opt: options.theme.notification.time, title: 'Time Stamp', type: 'color' }), + Option({ opt: options.theme.notification.text, title: 'Body Text', type: 'color' }), + Option({ opt: options.theme.notification.labelicon, title: 'Label Icon', subtitle: 'Icon that accompanies the label. Doesn\'t apply if icon is an app icon.', type: 'color' }), + Option({ opt: options.theme.notification.close_button.background, title: 'Dismiss Button', type: 'color' }), + Option({ opt: options.theme.notification.close_button.label, title: 'Dismiss Button Text', type: 'color' }), + ] + }) + }) +} diff --git a/widget/settings/shared.ts b/widget/settings/shared.ts new file mode 100644 index 0000000..e69de29 diff --git a/widget/settings/shared/Header.ts b/widget/settings/shared/Header.ts new file mode 100644 index 0000000..32079f1 --- /dev/null +++ b/widget/settings/shared/Header.ts @@ -0,0 +1,16 @@ +export const Header = (headerName: string) => { + return Widget.Box({ + class_name: "options-header", + children: [ + Widget.Label({ + class_name: "label-name", + label: headerName, + }), + Widget.Separator({ + vpack: "center", + hexpand: true, + class_name: "menu-separator", + }), + ], + }); +}; diff --git a/widget/settings/shared/Inputter.ts b/widget/settings/shared/Inputter.ts new file mode 100644 index 0000000..cc3b7ce --- /dev/null +++ b/widget/settings/shared/Inputter.ts @@ -0,0 +1,117 @@ +import { Opt } from "lib/option" +import Gdk from "gi://Gdk" +import icons from "lib/icons" + +export type RowProps = { + opt: Opt + title: string + note?: string + type?: + | "number" + | "color" + | "float" + | "object" + | "string" + | "enum" + | "boolean" + | "img" + | "font" + enums?: string[] + max?: number + min?: number +} + +const EnumSetter = (opt: Opt, values: string[]) => { + const lbl = Widget.Label({ label: opt.bind().as(v => `${v}`) }) + const step = (dir: 1 | -1) => { + const i = values.findIndex(i => i === lbl.label) + opt.setValue(dir > 0 + ? i + dir > values.length - 1 ? values[0] : values[i + dir] + : i + dir < 0 ? values[values.length - 1] : values[i + dir], + ) + } + const next = Widget.Button({ + child: Widget.Icon(icons.ui.arrow.right), + on_clicked: () => step(+1), + }) + const prev = Widget.Button({ + child: Widget.Icon(icons.ui.arrow.left), + on_clicked: () => step(-1), + }) + return Widget.Box({ + class_name: "enum-setter", + children: [lbl, prev, next], + }) +} + +export const Inputter = ({ + opt, + type = typeof opt.value as RowProps["type"], + enums, + max = 1000, + min = 0, +}: RowProps) => { + return Widget.Box({ + class_name: "inputter-container", + setup: self => { + + switch (type) { + case "number": return self.child = Widget.SpinButton({ + setup(self) { + self.set_range(min, max) + self.set_increments(1, 5) + self.on("value-changed", () => opt.value = self.value as T) + self.hook(opt, () => self.value = opt.value as number) + }, + }) + + case "float": + case "object": return self.child = Widget.Entry({ + on_accept: self => opt.value = JSON.parse(self.text || ""), + setup: self => self.hook(opt, () => self.text = JSON.stringify(opt.value)), + }) + + case "string": return self.child = Widget.Entry({ + on_accept: self => opt.value = self.text as T, + setup: self => self.hook(opt, () => self.text = opt.value as string), + }) + + case "enum": return self.child = EnumSetter(opt as unknown as Opt, enums!) + case "boolean": return self.child = Widget.Switch() + .on("notify::active", self => opt.value = self.active as T) + .hook(opt, self => self.active = opt.value as boolean) + + case "img": return self.child = Widget.FileChooserButton({ + on_file_set: ({ uri }) => { opt.value = uri!.replace("file://", "") as T }, + }) + + case "font": return self.child = Widget.FontButton({ + show_size: false, + use_size: false, + setup: self => self + .hook(opt, () => self.font = opt.value as string) + .on("font-set", ({ font }) => opt.value = font! + .split(" ").slice(0, -1).join(" ") as T), + }) + + case "color": return self.child = Widget.ColorButton() + .hook(opt, self => { + const rgba = new Gdk.RGBA() + rgba.parse(opt.value as string) + self.rgba = rgba + }) + .on("color-set", ({ rgba: { red, green, blue } }) => { + const hex = (n: number) => { + const c = Math.floor(255 * n).toString(16) + return c.length === 1 ? `0${c}` : c + } + opt.value = `#${hex(red)}${hex(green)}${hex(blue)}` as T + }) + + default: return self.child = Widget.Label({ + label: `no setter with type ${type}`, + }) + } + } + }) +} diff --git a/widget/settings/shared/Label.ts b/widget/settings/shared/Label.ts new file mode 100644 index 0000000..a6dad97 --- /dev/null +++ b/widget/settings/shared/Label.ts @@ -0,0 +1,20 @@ +export const Label = (name, sub = "") => { + return Widget.Box({ + vertical: true, + hpack: "start", + children: [ + Widget.Label({ + hpack: "start", + vpack: "center", + class_name: "options-label", + label: name + }), + Widget.Label({ + hpack: "start", + vpack: "center", + class_name: "options-sublabel", + label: sub + }), + ] + }) +} diff --git a/widget/settings/shared/Option.ts b/widget/settings/shared/Option.ts new file mode 100644 index 0000000..881d8ab --- /dev/null +++ b/widget/settings/shared/Option.ts @@ -0,0 +1,26 @@ +import { Label } from "./Label"; +import { Inputter } from "./Inputter"; +import icons from "lib/icons"; + +export const Option = (props) => { + return Widget.Box({ + class_name: "option-item", + hexpand: true, + children: [ + Widget.Box({ + hpack: "start", + vpack: "center", + hexpand: true, + child: Label(props.title, props.subtitle || ""), + }), + Inputter(props), + Widget.Button({ + vpack: "center", + class_name: "reset", + child: Widget.Icon(icons.ui.refresh), + on_clicked: () => props.opt.reset(), + sensitive: props.opt.bind().as(v => v !== props.opt.initial), + }), + ] + }) +}