diff --git a/src/app/commands/layerprops.rs b/src/app/commands/layerprops.rs index bb39b3a15..3943e0a33 100644 --- a/src/app/commands/layerprops.rs +++ b/src/app/commands/layerprops.rs @@ -673,9 +673,20 @@ impl OpenCADStudio { "BROWSER" => { self.show_browser ^= true; if self.show_browser { + // Dock it on first use rather than shipping it in the + // default layout: an existing user's edges should not + // rearrange themselves on upgrade for a panel they have + // not asked for. + let id = crate::ui::dock::PanelId::Browser; + if self.dock.location(id).is_none() { + // dock() clamps an out-of-range index to the end of + // the edge, so this appends below whatever is there. + self.dock + .dock(id, crate::app::config::DockSide::Right, usize::MAX); + } // Open expanded so it is usable straight away; the pin // button still collapses it. - self.dock_expanded = Some(crate::ui::dock::PanelId::Browser); + self.dock_expanded = Some(id); self.command_line .push_output(crate::t!("Browser opened.").as_ref()); } else { diff --git a/src/app/mod.rs b/src/app/mod.rs index 9204daedb..281daca5b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -736,6 +736,9 @@ pub(super) struct OpenCADStudio { /// Whether the Browser panel is shown. Off until BROWSER opens it, so /// the default layout is unchanged for existing users. pub(crate) show_browser: bool, + /// Which viewport background the colour wheel is editing, or `None` when + /// it is closed. One slot, because only one wheel can be open at a time. + pub(crate) bg_picker: Option, /// General edge-stack dock layout for the side panels. pub(crate) dock: crate::ui::dock::DockState, /// Which panel is currently floated at full height (hovered, or a pinned @@ -1444,6 +1447,14 @@ pub struct SaveOutcome { refreshed_preview: Option>, result: Result<(), crate::io::SaveFailure>, } +/// Which viewport background a colour-wheel session is editing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BgTarget { + Model, + Paper, + Desk, +} + /// Active page in the shared CAD colour picker. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ColorPickerTab { @@ -2129,6 +2140,12 @@ pub enum Message { ModelSpaceModeChanged(config::ModelSpaceMode), /// Change Model Space custom background color as hex or empty for default. ModelSpaceBgChanged(String), + /// Open the colour wheel on one of the viewport backgrounds. + BgPickerOpen(BgTarget), + /// Dismiss the wheel, changing nothing. + BgPickerCancel, + /// Accept the wheel's colour for whichever background it was opened on. + BgPickerSubmit(iced::Color), /// Change Paper Space custom sheet background color as hex or empty for default. PaperSpaceBgChanged(String), /// Change Paper Space desk surround background (#RRGGBB). @@ -3933,6 +3950,7 @@ impl OpenCADStudio { show_block_palette: false, show_external_references: false, show_browser: false, + bg_picker: None, block_palette: Default::default(), xref_manager: Default::default(), dock: Default::default(), diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index ee962d958..3573234d5 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -7068,6 +7068,39 @@ impl OpenCADStudio { Task::none() } + Message::BgPickerOpen(target) => { + self.bg_picker = Some(target); + Task::none() + } + + Message::BgPickerCancel => { + self.bg_picker = None; + Task::none() + } + + Message::BgPickerSubmit(color) => { + // Hand the result to the same handler the typed hex field + // uses, so the wheel and the field cannot drift apart on + // validation, persistence or the MatchTheme fallback. + let hex = crate::app::config::rgb_to_hex([ + (color.r * 255.0).round() as u8, + (color.g * 255.0).round() as u8, + (color.b * 255.0).round() as u8, + ]); + match self.bg_picker.take() { + Some(crate::app::BgTarget::Model) => { + Task::done(Message::ModelSpaceBgChanged(hex)) + } + Some(crate::app::BgTarget::Paper) => { + Task::done(Message::PaperSpaceBgChanged(hex)) + } + Some(crate::app::BgTarget::Desk) => { + Task::done(Message::DeskSpaceBgChanged(hex)) + } + None => Task::none(), + } + } + Message::ModelSpaceBgChanged(hex) => { self.model_bg_input = hex.clone(); if hex.trim().is_empty() { diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index d3bc809ab..80e96ac1d 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -358,6 +358,7 @@ impl OpenCADStudio { &self.model_bg_input, &self.paper_bg_input, &self.desk_bg_input, + self.bg_picker, flow, ) }, diff --git a/src/ui/dock.rs b/src/ui/dock.rs index 68b49c015..2be4829a2 100644 --- a/src/ui/dock.rs +++ b/src/ui/dock.rs @@ -121,7 +121,7 @@ impl Default for DockState { fn default() -> Self { Self { left: vec![PanelId::Properties], - right: vec![PanelId::Browser, PanelId::BlockPalette], + right: vec![PanelId::BlockPalette], panels: BTreeMap::new(), } } diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index a6664e345..1c1f35322 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -146,6 +146,43 @@ impl fmt::Display for Labelled { } } +/// A background swatch that opens the colour wheel when clicked. +/// +/// The wheel sits alongside the hex field rather than replacing it: typing +/// `#1E1E1E` stays the fastest way to reproduce an exact colour, while the +/// wheel is for choosing one by eye. +fn bg_swatch_picker<'a>( + target: crate::app::BgTarget, + rgb: [u8; 3], + open: Option, +) -> Element<'a, Message> { + let colour = iced::Color::from_rgb8(rgb[0], rgb[1], rgb[2]); + let swatch = container(Space::new()) + .width(28) + .height(22) + .style(move |theme: &Theme| container::Style { + background: Some(Background::Color(colour)), + border: Border { + color: theme.palette().background.strong.color, + width: 1.0, + radius: 3.0.into(), + }, + ..Default::default() + }); + let underlay = button(swatch) + .on_press(Message::BgPickerOpen(target)) + .padding(0) + .style(button::text); + iced_aw::ColorPicker::new( + open == Some(target), + colour, + underlay, + Message::BgPickerCancel, + Message::BgPickerSubmit, + ) + .into() +} + #[allow(clippy::too_many_arguments)] pub fn view_window<'a>( default_save_format: &'a str, @@ -172,6 +209,7 @@ pub fn view_window<'a>( model_bg_input: &'a str, paper_bg_input: &'a str, desk_bg_input: &'a str, + bg_picker: Option, sizing: crate::ui::modal::ModalSizing, ) -> Element<'a, Message> { let selected_format = crate::io::SAVE_FORMAT_OPTIONS @@ -455,58 +493,10 @@ pub fn view_window<'a>( }); let model_bg_rgb = model_space.custom_bg.unwrap_or(crate::app::config::CLASSIC_CAD_DARK_BG); - let model_bg_swatch = container(Space::new()) - .width(28) - .height(22) - .style(move |theme: &Theme| container::Style { - background: Some(Background::Color(iced::Color::from_rgb8( - model_bg_rgb[0], - model_bg_rgb[1], - model_bg_rgb[2], - ))), - border: Border { - color: theme.palette().background.strong.color, - width: 1.0, - radius: 3.0.into(), - }, - ..Default::default() - }); let paper_bg_rgb = model_space.custom_paper_bg.unwrap_or(crate::app::config::DEFAULT_PAPER_BG); - let paper_bg_swatch = container(Space::new()) - .width(28) - .height(22) - .style(move |theme: &Theme| container::Style { - background: Some(Background::Color(iced::Color::from_rgb8( - paper_bg_rgb[0], - paper_bg_rgb[1], - paper_bg_rgb[2], - ))), - border: Border { - color: theme.palette().background.strong.color, - width: 1.0, - radius: 3.0.into(), - }, - ..Default::default() - }); let desk_bg_rgb = model_space.custom_desk_bg.unwrap_or(crate::app::config::DEFAULT_DESK_BG); - let desk_bg_swatch = container(Space::new()) - .width(28) - .height(22) - .style(move |theme: &Theme| container::Style { - background: Some(Background::Color(iced::Color::from_rgb8( - desk_bg_rgb[0], - desk_bg_rgb[1], - desk_bg_rgb[2], - ))), - border: Border { - color: theme.palette().background.strong.color, - width: 1.0, - radius: 3.0.into(), - }, - ..Default::default() - }); let mode_options = crate::app::config::ModelSpaceMode::ALL .into_iter() @@ -641,7 +631,7 @@ pub fn view_window<'a>( display = display.push(Space::new().height(10)).push( row![ text(crate::t!("Model background")).size(12).width(140), - model_bg_swatch, + bg_swatch_picker(crate::app::BgTarget::Model, model_bg_rgb, bg_picker), text_input("#RRGGBB", model_bg_input) .on_input(Message::ModelSpaceBgChanged) .width(150), @@ -654,7 +644,7 @@ pub fn view_window<'a>( display = display.push(Space::new().height(10)).push( row![ text(crate::t!("Paper background")).size(12).width(140), - paper_bg_swatch, + bg_swatch_picker(crate::app::BgTarget::Paper, paper_bg_rgb, bg_picker), text_input("#RRGGBB", paper_bg_input) .on_input(Message::PaperSpaceBgChanged) .width(150), @@ -666,7 +656,7 @@ pub fn view_window<'a>( display = display.push(Space::new().height(10)).push( row![ text(crate::t!("Desk surround")).size(12).width(140), - desk_bg_swatch, + bg_swatch_picker(crate::app::BgTarget::Desk, desk_bg_rgb, bg_picker), text_input("#RRGGBB", desk_bg_input) .on_input(Message::DeskSpaceBgChanged) .width(150),