forked from 1b-agent/opencadstudio-upstream
Select Color: frameless gradient button, hex beside H/S/B, all Options swatches
- The gradient mode button is just its gradient, without a frame. - In the gradient view H, S and B sit on the left and Hex on the right of one row; the nearest index colour moves to the row below. - Every colour swatch in Options opens the Select Color panel: the six theme colours, crosshair, snap marker and command line text join the three viewport backgrounds. BgTarget becomes UiColor, and one color_swatch helper replaces the four hand-built swatches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
This commit is contained in:
6 files changed
+102
-125
No files matched your search
+12
-7
@@ -1577,12 +1577,17 @@ pub struct SaveOutcome {
|
||||
refreshed_preview: Option<Option<codec::Preview>>,
|
||||
result: Result<(), crate::io::SaveFailure>,
|
||||
}
|
||||
/// Which viewport background the interface colour picker is editing.
|
||||
/// Which Options colour the interface colour picker is editing.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BgTarget {
|
||||
Model,
|
||||
Paper,
|
||||
Desk,
|
||||
pub enum UiColor {
|
||||
ModelBackground,
|
||||
PaperBackground,
|
||||
DeskBackground,
|
||||
/// One of the six theme colours, by its row in Options.
|
||||
Theme(usize),
|
||||
Crosshair,
|
||||
SnapMarker,
|
||||
CommandText,
|
||||
}
|
||||
|
||||
/// Where a colour chosen in the standalone palette window should be applied.
|
||||
@@ -1607,8 +1612,8 @@ pub enum ColorPickTarget {
|
||||
LayerState(usize),
|
||||
/// The MText editor's selection (or global) colour.
|
||||
MText,
|
||||
/// An Options viewport background (interface colour, true colour only).
|
||||
Background(BgTarget),
|
||||
/// An Options interface colour (true colour only).
|
||||
Interface(UiColor),
|
||||
}
|
||||
|
||||
/// Table records the clipboard entities depend on, snapshotted from the source
|
||||
|
||||
@@ -11061,7 +11061,7 @@ impl OpenCADStudio {
|
||||
// the recent list.
|
||||
if !matches!(
|
||||
self.color_picker.as_ref().map(|picker| &picker.target),
|
||||
Some(crate::app::ColorPickTarget::Background(_))
|
||||
Some(crate::app::ColorPickTarget::Interface(_))
|
||||
) {
|
||||
self.note_recent_color(color);
|
||||
}
|
||||
|
||||
+12
-7
@@ -1136,7 +1136,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub(super) fn on_color_window_pick(&mut self, color: codec::types::Color) -> Task<Message> {
|
||||
if let Some(crate::app::ColorPickTarget::Background(target)) = self
|
||||
if let Some(crate::app::ColorPickTarget::Interface(target)) = self
|
||||
.color_picker
|
||||
.as_ref()
|
||||
.map(|picker| picker.target.clone())
|
||||
@@ -1146,13 +1146,18 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
};
|
||||
// Hand the result to the same handler the typed hex field uses, so
|
||||
// the picker and the field cannot drift apart on validation,
|
||||
// persistence or the MatchTheme fallback.
|
||||
// the picker and the field cannot drift apart on validation or
|
||||
// persistence.
|
||||
let hex = crate::app::config::rgb_to_hex([r, g, b]);
|
||||
use crate::app::UiColor;
|
||||
return Task::done(match target {
|
||||
crate::app::BgTarget::Model => Message::ModelSpaceBgChanged(hex),
|
||||
crate::app::BgTarget::Paper => Message::PaperSpaceBgChanged(hex),
|
||||
crate::app::BgTarget::Desk => Message::DeskSpaceBgChanged(hex),
|
||||
UiColor::ModelBackground => Message::ModelSpaceBgChanged(hex),
|
||||
UiColor::PaperBackground => Message::PaperSpaceBgChanged(hex),
|
||||
UiColor::DeskBackground => Message::DeskSpaceBgChanged(hex),
|
||||
UiColor::Theme(index) => Message::OptionsThemeColorChanged(index, hex),
|
||||
UiColor::Crosshair => Message::CrosshairColorChanged(hex),
|
||||
UiColor::SnapMarker => Message::SnapMarkerColorChanged(hex),
|
||||
UiColor::CommandText => Message::CommandTextColorChanged(hex),
|
||||
});
|
||||
}
|
||||
if matches!(
|
||||
@@ -1208,7 +1213,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
// Handled before this match.
|
||||
Some(
|
||||
crate::app::ColorPickTarget::PlotStyle | crate::app::ColorPickTarget::Background(_),
|
||||
crate::app::ColorPickTarget::PlotStyle | crate::app::ColorPickTarget::Interface(_),
|
||||
) => None,
|
||||
None => None,
|
||||
};
|
||||
|
||||
@@ -145,37 +145,46 @@ impl ColorEditor {
|
||||
let Hsb { h, s, b } = self.hsb;
|
||||
let (r, g, bl) = self.rgb;
|
||||
|
||||
let mode_button = |content: Element<'a, Message>, mode: EditorMode, padding: u16| {
|
||||
let mode_button = |content: Element<'a, Message>, mode: EditorMode| {
|
||||
let active = self.mode == mode;
|
||||
button(container(content).center(Length::Fill))
|
||||
.on_press(msg(EditorMsg::Mode(mode)))
|
||||
.width(52)
|
||||
.height(28)
|
||||
.padding(padding)
|
||||
.padding(0)
|
||||
.style(move |theme: &Theme, status| {
|
||||
super::picker::toggle_style(theme, active, status)
|
||||
})
|
||||
};
|
||||
// The field button is all gradient; it only takes colour when active.
|
||||
// The field button is only its gradient, without a frame; it takes
|
||||
// colour when its mode is active and stays gray otherwise.
|
||||
let field_active = self.mode == EditorMode::Field;
|
||||
let field_icon = container(Space::new())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(move |_theme: &Theme| {
|
||||
let field_button = button(Space::new().width(52).height(28))
|
||||
.on_press(msg(EditorMsg::Mode(EditorMode::Field)))
|
||||
.padding(0)
|
||||
.style(move |_theme: &Theme, _status| {
|
||||
let middle = if field_active {
|
||||
Color::from_rgb8(0x3A, 0xA0, 0xFF)
|
||||
} else {
|
||||
Color::from_rgb8(0x80, 0x80, 0x80)
|
||||
};
|
||||
gradient_box(
|
||||
let icon = gradient_box(
|
||||
3.0 * std::f32::consts::FRAC_PI_4,
|
||||
&[(0.0, Color::WHITE), (0.5, middle), (1.0, Color::BLACK)],
|
||||
)
|
||||
);
|
||||
button::Style {
|
||||
background: icon.background,
|
||||
border: Border {
|
||||
radius: 5.0.into(),
|
||||
..Border::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
let mut modes = row![
|
||||
mode_button(text("HSB").size(12).into(), EditorMode::Hsb, 0),
|
||||
mode_button(text("RGB").size(12).into(), EditorMode::Rgb, 0),
|
||||
mode_button(field_icon.into(), EditorMode::Field, 3),
|
||||
mode_button(text("HSB").size(12).into(), EditorMode::Hsb),
|
||||
mode_button(text("RGB").size(12).into(), EditorMode::Rgb),
|
||||
field_button,
|
||||
Space::new().width(Length::Fill),
|
||||
]
|
||||
.spacing(6);
|
||||
@@ -183,6 +192,26 @@ impl ColorEditor {
|
||||
modes = modes.push(end);
|
||||
}
|
||||
|
||||
let hex = self
|
||||
.hex_draft
|
||||
.clone()
|
||||
.unwrap_or_else(|| math::hex_string(r, g, bl));
|
||||
let hex_field = move || {
|
||||
row![
|
||||
text(t!("Hex")).size(11).style(super::picker::muted_text),
|
||||
text_input("", &hex)
|
||||
.on_input(|value| msg(EditorMsg::HexInput(value)))
|
||||
.on_submit(msg(EditorMsg::HexSubmit))
|
||||
.size(12)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.padding([5, 6])
|
||||
.width(Length::Fixed(74.0)),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
};
|
||||
let field_mode = self.mode == EditorMode::Field;
|
||||
|
||||
let editor: Element<'a, Message> = match self.mode {
|
||||
EditorMode::Hsb => column![
|
||||
channel("H", h, 360.0, Some('°'), hue_stops(), EditorMsg::Hue),
|
||||
@@ -270,13 +299,15 @@ impl ColorEditor {
|
||||
};
|
||||
column![
|
||||
row![area, strip].spacing(12),
|
||||
// H, S and B on the left, the hex value on the right.
|
||||
row![
|
||||
value("H", h, 360.0, '°', EditorMsg::Hue),
|
||||
Space::new().width(Length::Fill),
|
||||
value("S", s * 100.0, 100.0, '%', EditorMsg::Saturation),
|
||||
Space::new().width(Length::Fill),
|
||||
value("B", b * 100.0, 100.0, '%', EditorMsg::Brightness),
|
||||
Space::new().width(Length::Fill),
|
||||
hex_field(),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(10)
|
||||
@@ -284,23 +315,13 @@ impl ColorEditor {
|
||||
}
|
||||
};
|
||||
|
||||
let hex = self
|
||||
.hex_draft
|
||||
.clone()
|
||||
.unwrap_or_else(|| math::hex_string(r, g, bl));
|
||||
let mut hex_row = row![
|
||||
text(t!("Hex")).size(11).style(super::picker::muted_text),
|
||||
text_input("", &hex)
|
||||
.on_input(|value| msg(EditorMsg::HexInput(value)))
|
||||
.on_submit(msg(EditorMsg::HexSubmit))
|
||||
.size(12)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.padding([5, 6])
|
||||
.width(Length::Fixed(74.0)),
|
||||
Space::new().width(Length::Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center);
|
||||
// Below the editor: the hex value (unless the field view already
|
||||
// shows it) and, on the right, the nearest index colour.
|
||||
let mut hex_row = row![].spacing(8).align_y(iced::Center);
|
||||
if !field_mode {
|
||||
hex_row = hex_row.push(hex_field());
|
||||
}
|
||||
hex_row = hex_row.push(Space::new().width(Length::Fill));
|
||||
if let Some(end) = hex_end {
|
||||
hex_row = hex_row.push(end);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ impl ColorPicker {
|
||||
|
||||
/// Interface colours (Options backgrounds) only get the colour editor.
|
||||
fn editor_only(&self) -> bool {
|
||||
matches!(self.target, ColorPickTarget::Background(_))
|
||||
matches!(self.target, ColorPickTarget::Interface(_))
|
||||
}
|
||||
|
||||
fn set_pending(&mut self, color: AcadColor) {
|
||||
@@ -763,7 +763,7 @@ mod tests {
|
||||
#[test]
|
||||
fn interface_colours_show_only_the_editor() {
|
||||
let p = ColorPicker::new(
|
||||
ColorPickTarget::Background(crate::app::BgTarget::Model),
|
||||
ColorPickTarget::Interface(crate::app::UiColor::ModelBackground),
|
||||
AcadColor::Rgb {
|
||||
r: 30,
|
||||
g: 30,
|
||||
@@ -843,7 +843,7 @@ mod tests {
|
||||
),
|
||||
(
|
||||
"interface",
|
||||
ColorPickTarget::Background(crate::app::BgTarget::Model),
|
||||
ColorPickTarget::Interface(crate::app::UiColor::ModelBackground),
|
||||
AcadColor::Rgb {
|
||||
r: 33,
|
||||
g: 40,
|
||||
|
||||
+22
-76
@@ -157,13 +157,12 @@ impl<T> fmt::Display for Labelled<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A background swatch that opens the colour picker when clicked.
|
||||
/// A colour swatch that opens the Select Color panel when clicked.
|
||||
///
|
||||
/// The picker sits alongside the hex field rather than replacing it: typing
|
||||
/// The swatch sits beside the hex field rather than replacing it: typing
|
||||
/// `#1E1E1E` stays the fastest way to reproduce an exact colour, while the
|
||||
/// picker is for choosing one by eye.
|
||||
fn bg_swatch_picker<'a>(target: crate::app::BgTarget, rgb: [u8; 3]) -> Element<'a, Message> {
|
||||
let [r, g, b] = rgb;
|
||||
/// panel is for choosing one by eye.
|
||||
fn color_swatch<'a>(target: crate::app::UiColor, [r, g, b]: [u8; 3]) -> Element<'a, Message> {
|
||||
let swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
@@ -178,7 +177,7 @@ fn bg_swatch_picker<'a>(target: crate::app::BgTarget, rgb: [u8; 3]) -> Element<'
|
||||
});
|
||||
button(swatch)
|
||||
.on_press(Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::Background(target),
|
||||
crate::app::ColorPickTarget::Interface(target),
|
||||
codec::types::Color::Rgb { r, g, b },
|
||||
))
|
||||
.padding(0)
|
||||
@@ -298,18 +297,8 @@ pub fn view_window<'a>(
|
||||
|
||||
let mut color_controls = column![].spacing(8);
|
||||
for (index, (label, color)) in colors.into_iter().enumerate() {
|
||||
let swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let [r, g, bl, _] = color.into_rgba8();
|
||||
let swatch = color_swatch(crate::app::UiColor::Theme(index), [r, g, bl]);
|
||||
color_controls = color_controls.push(
|
||||
row![
|
||||
text(label).size(12).width(110),
|
||||
@@ -519,61 +508,18 @@ pub fn view_window<'a>(
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
let crosshair_rgb = crosshair_color.unwrap_or([255, 255, 255]);
|
||||
let crosshair_swatch =
|
||||
container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
crosshair_rgb[0],
|
||||
crosshair_rgb[1],
|
||||
crosshair_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let command_rgb = command_text_color.unwrap_or([220, 220, 220]);
|
||||
let command_swatch =
|
||||
container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
command_rgb[0],
|
||||
command_rgb[1],
|
||||
command_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let snap_rgb = snap_marker_color.unwrap_or([255, 230, 26]);
|
||||
let snap_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
snap_rgb[0],
|
||||
snap_rgb[1],
|
||||
snap_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let crosshair_swatch = color_swatch(
|
||||
crate::app::UiColor::Crosshair,
|
||||
crosshair_color.unwrap_or([255, 255, 255]),
|
||||
);
|
||||
let command_swatch = color_swatch(
|
||||
crate::app::UiColor::CommandText,
|
||||
command_text_color.unwrap_or([220, 220, 220]),
|
||||
);
|
||||
let snap_swatch = color_swatch(
|
||||
crate::app::UiColor::SnapMarker,
|
||||
snap_marker_color.unwrap_or([255, 230, 26]),
|
||||
);
|
||||
|
||||
let model_bg_rgb = model_space
|
||||
.custom_bg
|
||||
@@ -733,7 +679,7 @@ pub fn view_window<'a>(
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Model background")).size(12).width(140),
|
||||
bg_swatch_picker(crate::app::BgTarget::Model, model_bg_rgb),
|
||||
color_swatch(crate::app::UiColor::ModelBackground, model_bg_rgb),
|
||||
text_input("#RRGGBB", model_bg_input)
|
||||
.on_input(Message::ModelSpaceBgChanged)
|
||||
.width(150),
|
||||
@@ -746,7 +692,7 @@ pub fn view_window<'a>(
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Paper background")).size(12).width(140),
|
||||
bg_swatch_picker(crate::app::BgTarget::Paper, paper_bg_rgb),
|
||||
color_swatch(crate::app::UiColor::PaperBackground, paper_bg_rgb),
|
||||
text_input("#RRGGBB", paper_bg_input)
|
||||
.on_input(Message::PaperSpaceBgChanged)
|
||||
.width(150),
|
||||
@@ -758,7 +704,7 @@ pub fn view_window<'a>(
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Desk surround")).size(12).width(140),
|
||||
bg_swatch_picker(crate::app::BgTarget::Desk, desk_bg_rgb),
|
||||
color_swatch(crate::app::UiColor::DeskBackground, desk_bg_rgb),
|
||||
text_input("#RRGGBB", desk_bg_input)
|
||||
.on_input(Message::DeskSpaceBgChanged)
|
||||
.width(150),
|
||||
|
||||
Reference in new issue
Block a user