From 00af0335ffccfaf968b47fd763aefa0b6b440503 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Tue, 28 Jul 2026 18:38:30 +0200 Subject: [PATCH] Use slider toggles for boolean controls --- AGENTS.md | 1 + src/app/view.rs | 10 +++++++--- src/app/view/a2ui.rs | 8 ++++---- src/app/view/git.rs | 2 +- src/app/view/preferences.rs | 30 +++++++++++++++--------------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c9b2c4d..de94a74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,7 @@ - Follow the Codex macOS dark UI: near-black backgrounds, subtly raised surfaces, quiet borders, rounded corners, and restrained contrast. - Present overviews as one bordered panel with divided rows, a clear primary label, muted supporting text, and compact trailing actions. - Reuse the shared surface and action-button styles in `src/app/view.rs`; keep button shape, padding, typography, hover, and disabled states consistent across windows. Destructive actions may differ by color only, while navigation controls may remain flat. +- Render boolean controls as macOS-style slider toggles through the shared `toggle` helper, not checkboxes. - Prefer generous spacing and clear hierarchy over decoration; avoid one-off colors, card stacks, oversized controls, and screen-specific button styling. ## Commit gates diff --git a/src/app/view.rs b/src/app/view.rs index 5d80989..73077f2 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -20,9 +20,9 @@ use crate::model::{ use crate::settings::{GIB, REASONING_MODES}; use iced::theme::{Palette, palette}; use iced::widget::{ - Button, Space, Svg, Tooltip, button, checkbox, column, container, image, markdown, mouse_area, - opaque, pick_list, progress_bar, row, rule, scrollable, slider, stack, svg, text, text_editor, - text_input, tooltip, + Button, Space, Svg, Tooltip, button, column, container, image, markdown, mouse_area, opaque, + pick_list, progress_bar, row, rule, scrollable, slider, stack, svg, text, text_editor, + text_input, toggler, tooltip, }; use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Theme, window}; use std::collections::VecDeque; @@ -984,6 +984,10 @@ fn danger_button<'a>(content: impl Into>) -> Button<'a, Mes button(content).padding([8, 14]).style(danger_button_style) } +fn toggle<'a>(checked: bool) -> iced::widget::Toggler<'a, Message> { + toggler(checked).size(18).spacing(8) +} + fn action_button_style(_: &Theme, status: button::Status) -> button::Style { let (background, text_color) = match status { button::Status::Active | button::Status::Pressed => { diff --git a/src/app/view/a2ui.rs b/src/app/view/a2ui.rs index 41ed435..471d2e8 100644 --- a/src/app/view/a2ui.rs +++ b/src/app/view/a2ui.rs @@ -495,10 +495,10 @@ impl App { &surface.data, context, )); - let checkbox = checkbox(checked).label(label); + let control = toggle(checked).label(label); if let Some(path) = local_path(binding_path(binding), context_path.as_deref()) { let surface_id = surface.id.clone(); - checkbox + control .on_toggle(move |value| { Message::A2uiDataChanged( surface_id.clone(), @@ -508,7 +508,7 @@ impl App { }) .into() } else { - checkbox.into() + control.into() } } "Slider" => { @@ -617,7 +617,7 @@ impl App { } chips = chips.push(choice); } else if multiple { - let mut choice = checkbox(is_selected).label(label); + let mut choice = toggle(is_selected).label(label); if let Some(path) = path.clone() { let surface_id = surface.id.clone(); let option_value = option_value.clone(); diff --git a/src/app/view/git.rs b/src/app/view/git.rs index bbf0105..c839255 100644 --- a/src/app/view/git.rs +++ b/src/app/view/git.rs @@ -59,7 +59,7 @@ impl App { file_list = file_list.push(rule::horizontal(1)).push( container( row![ - checkbox(selected).on_toggle({ + toggle(selected).on_toggle({ let path = path.clone(); move |_| Message::ToggleGitFile(path.clone()) }), diff --git a/src/app/view/preferences.rs b/src/app/view/preferences.rs index 6ddc54b..6c43d41 100644 --- a/src/app/view/preferences.rs +++ b/src/app/view/preferences.rs @@ -9,7 +9,7 @@ impl App { .supports_dspark() .then_some(Message::PreferenceLegacyMtpChanged); let legacy_mtp = hint( - checkbox(self.preference_draft.legacy_mtp_enabled) + toggle(self.preference_draft.legacy_mtp_enabled) .label("Enable legacy MTP for this model") .on_toggle_maybe(legacy_mtp_toggle), "Uses the managed one-stage MTP support GGUF. The target model verifies every drafted token; it is mutually exclusive with DSpark.", @@ -20,7 +20,7 @@ impl App { .supports_dspark() .then_some(Message::PreferenceDsparkChanged); let dspark = hint( - checkbox(self.preference_draft.dspark_enabled) + toggle(self.preference_draft.dspark_enabled) .label("Enable DSpark for this model") .on_toggle_maybe(dspark_toggle), "Speculative decoding with the managed DSpark draft artifact: a small model proposes tokens that the main model verifies in one pass. Usually a large speedup; the target model may also stream routed experts from SSD.", @@ -121,7 +121,7 @@ impl App { .align_y(Alignment::Center), text("Enter a whole number from 1 to 1440.").size(12), hint( - checkbox(self.preference_draft.a2ui_enabled) + toggle(self.preference_draft.a2ui_enabled) .label("Enable interactive A2UI chat surfaces") .on_toggle(Message::PreferenceA2uiChanged), "Lets the local model build validated native charts, tables, forms and other interactive chat surfaces. Turning it off removes the A2UI catalog from the system prompt.", @@ -134,7 +134,7 @@ impl App { "LOCAL ENDPOINT", column![ hint( - checkbox(self.preference_draft.endpoint_enabled) + toggle(self.preference_draft.endpoint_enabled) .label("Enable OpenAI-compatible endpoint") .on_toggle(Message::PreferenceEndpointEnabledChanged), "Serves the loaded model over an OpenAI-style HTTP API, so editors, scripts and agents on this machine can use it. Turned off, only this window can generate.", @@ -146,7 +146,7 @@ impl App { .on_input(Message::PreferenceEndpointPortChanged), ), hint( - checkbox(self.preference_draft.endpoint_cors) + toggle(self.preference_draft.endpoint_cors) .label("Allow browser clients (CORS)") .on_toggle(Message::PreferenceEndpointCorsChanged), "Answers with permissive CORS headers so JavaScript running in a web page may call the endpoint. Leave it off when only native tools connect.", @@ -161,7 +161,7 @@ impl App { "DEV BRAIN", column![ hint( - checkbox(self.preference_draft.dev_brain_enabled) + toggle(self.preference_draft.dev_brain_enabled) .label("Enable project-backed LLM wiki") .on_toggle(Message::PreferenceDevBrainEnabledChanged), "Lets the agent use its normal file tools on managed pages in a dedicated Obsidian vault, with indexed search and validation. Disabled means no Dev Brain access or prompt instructions.", @@ -228,13 +228,13 @@ impl App { .spacing(12) .align_y(Alignment::Center), hint( - checkbox(self.preference_draft.git_indent_heuristic) + toggle(self.preference_draft.git_indent_heuristic) .label("Use indentation heuristic") .on_toggle(Message::PreferenceGitIndentHeuristicChanged), "Shift ambiguous hunk boundaries toward indentation changes, which usually makes source-code diffs easier to read.", ), hint( - checkbox(self.preference_draft.git_ignore_blank_lines) + toggle(self.preference_draft.git_ignore_blank_lines) .label("Ignore blank-line changes") .on_toggle(Message::PreferenceGitIgnoreBlankLinesChanged), "Hide hunks whose changed lines are all blank.", @@ -348,13 +348,13 @@ impl App { prefill, ), hint( - checkbox(self.preference_draft.quality) + toggle(self.preference_draft.quality) .label("Prefer exact quality kernels") .on_toggle(Message::PreferenceQualityChanged), "Runs the exact Metal kernels instead of the fast approximations. Slightly slower, and it removes the small numeric differences those approximations introduce.", ), hint( - checkbox(self.preference_draft.warm_weights) + toggle(self.preference_draft.warm_weights) .label("Warm mapped weights at load time") .on_toggle(Message::PreferenceWarmWeightsChanged), "Reads every mapped weight page once at load, so the first reply is not interrupted by page faults from disk. Loading takes longer and memory pressure rises immediately.", @@ -402,13 +402,13 @@ impl App { .on_input(Message::PreferenceMtpMarginChanged), ), hint( - checkbox(self.preference_draft.glm_mtp) + toggle(self.preference_draft.glm_mtp) .label("Enable integrated GLM MTP") .on_toggle_maybe(glm_mtp_toggle), "Uses the prediction head built into GLM 5.2 for speculative decoding, so no separate draft model is loaded. Available for GLM 5.2 only.", ), hint( - checkbox(self.preference_draft.glm_mtp_timing) + toggle(self.preference_draft.glm_mtp_timing) .label("Log GLM MTP timing counters") .on_toggle_maybe(glm_mtp_timing_toggle), "Records per-stage timings of the speculative path to the log, to show where the acceleration actually goes. A diagnostic aid that costs a little throughput.", @@ -421,7 +421,7 @@ impl App { dspark_confidence, ), hint( - checkbox(self.preference_draft.dspark_strict) + toggle(self.preference_draft.dspark_strict) .label("DSpark target-only decode") .on_toggle_maybe(dspark_strict_toggle), "Lets the draft model only propose, never decide: every token is sampled by the full model. Gives up some of the speedup in exchange for output identical to non-speculative decoding.", @@ -457,13 +457,13 @@ impl App { Space::new().height(6), text("SSD STREAMING").size(11).color(muted_text()), hint( - checkbox(self.preference_draft.ssd_streaming) + toggle(self.preference_draft.ssd_streaming) .label("Enable SSD-backed model streaming") .on_toggle(Message::PreferenceSsdChanged), "Leaves the routed expert weights on disk and pages them in as they are needed, so a model larger than this machine's memory still runs. Every cache miss waits for the SSD; speculative support weights remain resident while target experts stream.", ), hint( - checkbox(self.preference_draft.ssd_streaming_cold) + toggle(self.preference_draft.ssd_streaming_cold) .label("Skip automatic expert preload") .on_toggle(Message::PreferenceSsdColdChanged), "Starts with an empty expert cache instead of reading the likely experts up front. The model is ready sooner and uses less memory, at the price of slow first replies.",