feat(ui): add configurable Iced themes
This commit is contained in:
parent
73545520c8
commit
4bf14baea4
52 changed files with 3058 additions and 5665 deletions
|
|
@ -18,6 +18,8 @@ use crate::ui::window::plot::PlotDialogState;
|
|||
pub struct AppConfig {
|
||||
/// Input modes, backup, plugin lists, viewport background colours, …
|
||||
pub settings: UserSettings,
|
||||
/// Iced theme selection and the six base colours used by a custom theme.
|
||||
pub theme: UiThemeConfig,
|
||||
/// Recent-files list + retained count.
|
||||
pub recent: RecentConfig,
|
||||
/// Last selected section on the tabbed Start page.
|
||||
|
|
@ -35,6 +37,7 @@ impl Default for AppConfig {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
settings: UserSettings::default(),
|
||||
theme: UiThemeConfig::default(),
|
||||
recent: RecentConfig::default(),
|
||||
start: StartConfig::default(),
|
||||
statusbar: StatusBarConfig::default(),
|
||||
|
|
@ -44,6 +47,136 @@ impl Default for AppConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct UiThemeConfig {
|
||||
pub name: String,
|
||||
pub palette: UiThemePalette,
|
||||
}
|
||||
|
||||
impl Default for UiThemeConfig {
|
||||
fn default() -> Self {
|
||||
let theme = iced::Theme::Dark;
|
||||
Self {
|
||||
name: theme.to_string(),
|
||||
palette: UiThemePalette::from_iced(theme.palette()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UiThemeConfig {
|
||||
pub fn to_iced(&self) -> iced::Theme {
|
||||
if self.name == "Custom" {
|
||||
iced::Theme::custom("Custom", self.palette.to_iced())
|
||||
} else {
|
||||
builtin_theme(&self.name).unwrap_or(iced::Theme::Dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct UiThemePalette {
|
||||
pub background: [u8; 3],
|
||||
pub text: [u8; 3],
|
||||
pub primary: [u8; 3],
|
||||
pub success: [u8; 3],
|
||||
pub warning: [u8; 3],
|
||||
pub danger: [u8; 3],
|
||||
}
|
||||
|
||||
impl Default for UiThemePalette {
|
||||
fn default() -> Self {
|
||||
Self::from_iced(iced::Theme::Dark.palette())
|
||||
}
|
||||
}
|
||||
|
||||
impl UiThemePalette {
|
||||
pub fn from_iced(palette: iced::theme::Palette) -> Self {
|
||||
Self {
|
||||
background: color_to_rgb(palette.background),
|
||||
text: color_to_rgb(palette.text),
|
||||
primary: color_to_rgb(palette.primary),
|
||||
success: color_to_rgb(palette.success),
|
||||
warning: color_to_rgb(palette.warning),
|
||||
danger: color_to_rgb(palette.danger),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_iced(self) -> iced::theme::Palette {
|
||||
iced::theme::Palette {
|
||||
background: rgb_to_color(self.background),
|
||||
text: rgb_to_color(self.text),
|
||||
primary: rgb_to_color(self.primary),
|
||||
success: rgb_to_color(self.success),
|
||||
warning: rgb_to_color(self.warning),
|
||||
danger: rgb_to_color(self.danger),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hex_values(self) -> [String; 6] {
|
||||
[
|
||||
rgb_to_hex(self.background),
|
||||
rgb_to_hex(self.text),
|
||||
rgb_to_hex(self.primary),
|
||||
rgb_to_hex(self.success),
|
||||
rgb_to_hex(self.warning),
|
||||
rgb_to_hex(self.danger),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn set_hex(&mut self, index: usize, value: &str) -> bool {
|
||||
let Some(rgb) = parse_hex(value) else {
|
||||
return false;
|
||||
};
|
||||
match index {
|
||||
0 => self.background = rgb,
|
||||
1 => self.text = rgb,
|
||||
2 => self.primary = rgb,
|
||||
3 => self.success = rgb,
|
||||
4 => self.warning = rgb,
|
||||
5 => self.danger = rgb,
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_theme(name: &str) -> Option<iced::Theme> {
|
||||
iced::Theme::ALL
|
||||
.iter()
|
||||
.find(|theme| theme.to_string() == name)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn color_to_rgb(color: iced::Color) -> [u8; 3] {
|
||||
[
|
||||
(color.r * 255.0).round() as u8,
|
||||
(color.g * 255.0).round() as u8,
|
||||
(color.b * 255.0).round() as u8,
|
||||
]
|
||||
}
|
||||
|
||||
fn rgb_to_color(rgb: [u8; 3]) -> iced::Color {
|
||||
iced::Color::from_rgb8(rgb[0], rgb[1], rgb[2])
|
||||
}
|
||||
|
||||
fn rgb_to_hex(rgb: [u8; 3]) -> String {
|
||||
format!("#{:02X}{:02X}{:02X}", rgb[0], rgb[1], rgb[2])
|
||||
}
|
||||
|
||||
fn parse_hex(value: &str) -> Option<[u8; 3]> {
|
||||
let value = value.trim().strip_prefix('#').unwrap_or(value.trim());
|
||||
if value.len() != 6 {
|
||||
return None;
|
||||
}
|
||||
Some([
|
||||
u8::from_str_radix(&value[0..2], 16).ok()?,
|
||||
u8::from_str_radix(&value[2..4], 16).ok()?,
|
||||
u8::from_str_radix(&value[4..6], 16).ok()?,
|
||||
])
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct RecentConfig {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod alias;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod automation;
|
||||
mod config;
|
||||
pub(crate) mod config;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use automation::{export_headless, serve};
|
||||
mod command_driver;
|
||||
|
|
@ -688,6 +688,8 @@ pub(super) struct OpenCADStudio {
|
|||
|
||||
// ── Color Scheme ──────────────────────────────────────────────────────
|
||||
active_theme: Theme,
|
||||
ui_theme: config::UiThemeConfig,
|
||||
theme_color_inputs: [String; 6],
|
||||
|
||||
// ── Keyboard Shortcut Editor ──────────────────────────────────────────
|
||||
/// User-defined function-key overrides: "F3" → command string.
|
||||
|
|
@ -1474,6 +1476,10 @@ pub enum Message {
|
|||
OptionsOpen,
|
||||
/// Set the default type/version used when first saving a new drawing.
|
||||
DefaultSaveFormatChanged(String),
|
||||
/// Select one of Iced's built-in themes or the editable Custom theme.
|
||||
OptionsThemeChanged(String),
|
||||
/// Edit one of Custom theme's six base colours as #RRGGBB.
|
||||
OptionsThemeColorChanged(usize, String),
|
||||
ClearScene,
|
||||
SetWireframe(bool),
|
||||
/// Set the active tab's render mode (one of acadrust's seven visual
|
||||
|
|
@ -2626,6 +2632,8 @@ impl OpenCADStudio {
|
|||
active_plot_style: None,
|
||||
// Color scheme (default: dark CAD-style)
|
||||
active_theme: Theme::Dark,
|
||||
ui_theme: config::UiThemeConfig::default(),
|
||||
theme_color_inputs: config::UiThemePalette::default().hex_values(),
|
||||
// Keyboard shortcuts
|
||||
shortcut_overrides: rustc_hash::FxHashMap::default(),
|
||||
// Command aliases (populated from ocad.pgp just after construction)
|
||||
|
|
|
|||
|
|
@ -320,6 +320,7 @@ impl OpenCADStudio {
|
|||
pub(in crate::app) fn current_config(&self) -> crate::app::config::AppConfig {
|
||||
crate::app::config::AppConfig {
|
||||
settings: self.current_settings(),
|
||||
theme: self.ui_theme.clone(),
|
||||
recent: crate::app::config::RecentConfig {
|
||||
files: self
|
||||
.recent_files
|
||||
|
|
@ -342,6 +343,9 @@ impl OpenCADStudio {
|
|||
/// Distribute a loaded config into live app state (called once at startup).
|
||||
pub(in crate::app) fn apply_config(&mut self, cfg: crate::app::config::AppConfig) {
|
||||
self.apply_settings(&cfg.settings);
|
||||
self.ui_theme = cfg.theme.clone();
|
||||
self.active_theme = self.ui_theme.to_iced();
|
||||
self.theme_color_inputs = self.ui_theme.palette.hex_values();
|
||||
self.recent_files = cfg
|
||||
.recent
|
||||
.files
|
||||
|
|
|
|||
|
|
@ -3551,7 +3551,12 @@ impl OpenCADStudio {
|
|||
}
|
||||
|
||||
Message::SetTheme(theme) => {
|
||||
self.ui_theme.name = theme.to_string();
|
||||
self.ui_theme.palette =
|
||||
crate::app::config::UiThemePalette::from_iced(theme.palette());
|
||||
self.theme_color_inputs = self.ui_theme.palette.hex_values();
|
||||
self.active_theme = theme;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
@ -3620,6 +3625,37 @@ impl OpenCADStudio {
|
|||
Message::DefaultSaveFormatChanged(format) => {
|
||||
self.default_save_format =
|
||||
crate::io::canonical_save_format(&format).to_string();
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::OptionsThemeChanged(name) => {
|
||||
self.ui_theme.name = name;
|
||||
if let Some(theme) =
|
||||
crate::app::config::builtin_theme(&self.ui_theme.name)
|
||||
{
|
||||
self.ui_theme.palette =
|
||||
crate::app::config::UiThemePalette::from_iced(theme.palette());
|
||||
self.theme_color_inputs = self.ui_theme.palette.hex_values();
|
||||
self.active_theme = theme;
|
||||
} else {
|
||||
self.ui_theme.name = "Custom".to_string();
|
||||
self.active_theme = self.ui_theme.to_iced();
|
||||
}
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::OptionsThemeColorChanged(index, value) => {
|
||||
if index >= self.theme_color_inputs.len() {
|
||||
return Task::none();
|
||||
}
|
||||
self.theme_color_inputs[index] = value.clone();
|
||||
if self.ui_theme.palette.set_hex(index, &value) {
|
||||
self.ui_theme.name = "Custom".to_string();
|
||||
self.active_theme = self.ui_theme.to_iced();
|
||||
self.persist_settings_if_changed();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use super::super::Message;
|
|||
use iced::widget::{
|
||||
button, container, mouse_area, row,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Theme};
|
||||
use iced::{Background, Border, Element, Theme};
|
||||
|
||||
pub(super) fn viewport_controls<'a>(
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
|
|
@ -23,64 +23,58 @@ pub(super) fn viewport_controls<'a>(
|
|||
RenderModeChoice(M::FlatShadedWithEdges),
|
||||
RenderModeChoice(M::GouraudShadedWithEdges),
|
||||
];
|
||||
let light = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
|
||||
let accent = Color { r: 0.45, g: 0.70, b: 1.0, a: 1.0 };
|
||||
let green = Color { r: 0.36, g: 0.80, b: 0.45, a: 1.0 };
|
||||
let red = Color { r: 0.92, g: 0.38, b: 0.38, a: 1.0 };
|
||||
|
||||
// Fixed-colour icon button (close = red); colour stays on hover.
|
||||
let tinted_btn = move |bytes: &'static [u8], color: Color, msg: Message| {
|
||||
button(crate::ui::icons::tinted(bytes, 15.0, color))
|
||||
let danger_btn = move |bytes: &'static [u8], msg: Message| {
|
||||
button(crate::ui::icons::themed_danger(bytes, 15.0))
|
||||
.on_press(msg)
|
||||
.padding([4, 6])
|
||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
.style(move |theme: &Theme, status| iced::widget::button::Style {
|
||||
background: matches!(
|
||||
status,
|
||||
iced::widget::button::Status::Hovered
|
||||
| iced::widget::button::Status::Pressed => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 0.9,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
| iced::widget::button::Status::Pressed
|
||||
)
|
||||
.then_some(Background::Color(
|
||||
theme.extended_palette().danger.weak.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_color: color,
|
||||
text_color: theme.extended_palette().danger.base.color,
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
|
||||
// Borderless icon button; an `active` toggle gets an accent tint + fill.
|
||||
let icon_btn = move |bytes: &'static [u8], active: bool, msg: Message| {
|
||||
let tint = if active { accent } else { light };
|
||||
button(crate::ui::icons::tinted(bytes, 15.0, tint))
|
||||
let icon = if active {
|
||||
crate::ui::icons::themed_primary(bytes, 15.0)
|
||||
} else {
|
||||
crate::ui::icons::themed(bytes, 15.0)
|
||||
};
|
||||
button(icon)
|
||||
.on_press(msg)
|
||||
.padding([4, 6])
|
||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
||||
background: Some(Background::Color(match (active, status) {
|
||||
(_, iced::widget::button::Status::Hovered) => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 0.9,
|
||||
},
|
||||
(true, _) => Color {
|
||||
r: 0.16,
|
||||
g: 0.22,
|
||||
b: 0.32,
|
||||
a: 0.9,
|
||||
},
|
||||
(false, _) => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (active, status) {
|
||||
(_, iced::widget::button::Status::Hovered) => {
|
||||
Some(palette.background.strong)
|
||||
}
|
||||
(true, _) => Some(palette.primary.weak),
|
||||
(false, _) => None,
|
||||
};
|
||||
iced::widget::button::Style {
|
||||
background: pair.map(|p| Background::Color(p.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_color: tint,
|
||||
text_color: pair
|
||||
.map(|p| p.text)
|
||||
.unwrap_or(palette.background.base.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
|
@ -92,27 +86,27 @@ pub(super) fn viewport_controls<'a>(
|
|||
)
|
||||
.text_size(11)
|
||||
.padding([4, 6])
|
||||
.style(move |_: &Theme, _| iced::widget::pick_list::Style {
|
||||
background: Background::Color(Color::TRANSPARENT),
|
||||
.style(move |theme: &Theme, _| {
|
||||
let text = theme.extended_palette().background.base.text;
|
||||
iced::widget::pick_list::Style {
|
||||
background: Background::Color(iced::Color::TRANSPARENT),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_color: light,
|
||||
placeholder_color: light,
|
||||
handle_color: light,
|
||||
text_color: text,
|
||||
placeholder_color: text.scale_alpha(0.68),
|
||||
handle_color: text,
|
||||
}
|
||||
});
|
||||
|
||||
// Thin vertical divider between control groups.
|
||||
let sep = || {
|
||||
container(iced::widget::Space::new().width(1.0).height(16.0)).style(|_: &Theme| {
|
||||
container(iced::widget::Space::new().width(1.0).height(16.0)).style(|theme: &Theme| {
|
||||
iced::widget::container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 0.7,
|
||||
})),
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color.scale_alpha(0.7)
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
|
|
@ -139,7 +133,7 @@ pub(super) fn viewport_controls<'a>(
|
|||
// would only fire on release). Placed just left of Close.
|
||||
if tile_count > 1 {
|
||||
let drag = mouse_area(
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::MOVE, 15.0, green))
|
||||
container(crate::ui::icons::themed_success(crate::ui::icons::MOVE, 15.0))
|
||||
.padding([4, 6])
|
||||
.style(|_: &Theme| iced::widget::container::Style {
|
||||
border: Border {
|
||||
|
|
@ -155,30 +149,25 @@ pub(super) fn viewport_controls<'a>(
|
|||
.push(sep())
|
||||
.push(drag)
|
||||
.push(sep())
|
||||
.push(tinted_btn(crate::ui::icons::CLOSE, red, Message::CloseModelViewport));
|
||||
.push(danger_btn(crate::ui::icons::CLOSE, Message::CloseModelViewport));
|
||||
}
|
||||
}
|
||||
|
||||
container(bar)
|
||||
.padding(2)
|
||||
.style(|_: &Theme| iced::widget::container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 0.75,
|
||||
})),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
iced::widget::container::Style {
|
||||
background: Some(Background::Color(
|
||||
palette.background.weak.color.scale_alpha(0.92)
|
||||
)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
use super::super::{Message, OpenCADStudio};
|
||||
use iced::widget::{button, column, container, pick_list, row, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Title shown in the active modal's title bar, left of the move/close
|
||||
|
|
@ -63,9 +63,13 @@ impl OpenCADStudio {
|
|||
sized(crate::ui::window::alias_editor::view_window(&self.alias_editor_rows), 480, 520)
|
||||
}
|
||||
super::super::ModalKind::Options => sized(
|
||||
crate::ui::window::options::view_window(&self.default_save_format),
|
||||
480,
|
||||
190,
|
||||
crate::ui::window::options::view_window(
|
||||
&self.default_save_format,
|
||||
&self.ui_theme,
|
||||
&self.theme_color_inputs,
|
||||
),
|
||||
520,
|
||||
500,
|
||||
),
|
||||
super::super::ModalKind::PluginManager => sized(
|
||||
crate::ui::window::plugin_manager::view_window(
|
||||
|
|
@ -712,125 +716,56 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
|
||||
fn dialog_button(
|
||||
label: &'static str,
|
||||
message: Message,
|
||||
style: fn(&Theme, button::Status) -> button::Style,
|
||||
) -> Element<'static, Message> {
|
||||
button(text(label).size(13))
|
||||
.on_press(message)
|
||||
.style(style)
|
||||
.padding([6, 18])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn dialog_body_style(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
text_color: Some(palette.background.base.text),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn dialog_muted_text_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compact Save-As options dialog: pick the format/version and a default file
|
||||
/// name. The destination folder and overwrite confirmation come from the
|
||||
/// native OS save dialog (native) or the browser download (web) that follows.
|
||||
fn save_as_dialog_window<'a>(filename: &'a str, format: &'a str) -> Element<'a, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.58,
|
||||
g: 0.58,
|
||||
b: 0.62,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_OK: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_HOV: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.55,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_GREY: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.29,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_GHOV: Color = Color {
|
||||
r: 0.34,
|
||||
g: 0.34,
|
||||
b: 0.38,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let btn = |lbl: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(lbl).size(12).color(TEXT))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(
|
||||
if matches!(st, button::Status::Hovered | button::Status::Pressed) {
|
||||
hov
|
||||
} else {
|
||||
base
|
||||
},
|
||||
)),
|
||||
text_color: TEXT,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 12])
|
||||
};
|
||||
|
||||
let sel_fmt = crate::io::SAVE_FORMAT_OPTIONS
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|&s| s == format);
|
||||
let label = |s: &'static str| text(s).size(11).color(DIM);
|
||||
let label = |s: &'static str| text(s).size(11).style(dialog_muted_text_style);
|
||||
|
||||
let mut items: Vec<Element<'a, Message>> = Vec::new();
|
||||
items.push(text("Save Drawing As").size(14).color(TEXT).into());
|
||||
items.push(text("Save Drawing As").size(14).into());
|
||||
items.push(Space::new().height(12).into());
|
||||
|
||||
// Web has no native file dialog, so the file name is typed here. On native
|
||||
// the OS save dialog collects the name, so this field is omitted.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
const INPUT_BG: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
let input_sty =
|
||||
|_: &Theme, _: iced::widget::text_input::Status| iced::widget::text_input::Style {
|
||||
background: Background::Color(INPUT_BG),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 0.45,
|
||||
},
|
||||
};
|
||||
items.push(
|
||||
row![
|
||||
label("File name:").width(70),
|
||||
iced::widget::text_input("drawing.dwg", filename)
|
||||
.on_input(Message::SaveDialogFilenameChanged)
|
||||
.style(input_sty)
|
||||
.size(13)
|
||||
.padding([5, 8])
|
||||
.width(Fill),
|
||||
|
|
@ -860,9 +795,9 @@ fn save_as_dialog_window<'a>(filename: &'a str, format: &'a str) -> Element<'a,
|
|||
items.push(
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
btn("Save as...", Message::SaveDialogConfirm, BTN_OK, BTN_HOV),
|
||||
dialog_button("Save as...", Message::SaveDialogConfirm, button::primary),
|
||||
Space::new().width(8),
|
||||
btn("Cancel", Message::SaveDialogCancel, BTN_GREY, BTN_GHOV),
|
||||
dialog_button("Cancel", Message::SaveDialogCancel, button::secondary),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
|
|
@ -870,10 +805,7 @@ fn save_as_dialog_window<'a>(filename: &'a str, format: &'a str) -> Element<'a,
|
|||
let body = column(items).spacing(0);
|
||||
|
||||
container(body)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.padding([14, 16])
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -881,89 +813,23 @@ fn save_as_dialog_window<'a>(filename: &'a str, format: &'a str) -> Element<'a,
|
|||
}
|
||||
|
||||
fn unsaved_changes_dialog_window(name: &str) -> Element<'static, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COL: Color = Color {
|
||||
r: 0.38,
|
||||
g: 0.38,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COL: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_SAVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_HOVER: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.55,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_DISC: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_DHOV: Color = Color {
|
||||
r: 0.36,
|
||||
g: 0.36,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let body_text = format!("Do you want to save changes to \"{}\"?", name);
|
||||
|
||||
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(label).size(13).color(TEXT_COL))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => hov,
|
||||
_ => base,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border {
|
||||
color: BORDER_COL,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.padding([6, 18])
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
text(body_text).size(13).color(TEXT_COL),
|
||||
text(body_text).size(13),
|
||||
iced::widget::Space::new().height(20),
|
||||
row![
|
||||
btn("Save", Message::UnsavedDialogSave, BTN_SAVE, BTN_HOVER),
|
||||
dialog_button("Save", Message::UnsavedDialogSave, button::primary),
|
||||
iced::widget::Space::new().width(8),
|
||||
btn("Discard", Message::UnsavedDialogDiscard, BTN_DISC, BTN_DHOV),
|
||||
dialog_button("Discard", Message::UnsavedDialogDiscard, button::danger),
|
||||
iced::widget::Space::new().width(8),
|
||||
btn("Cancel", Message::UnsavedDialogCancel, BTN_DISC, BTN_DHOV),
|
||||
dialog_button("Cancel", Message::UnsavedDialogCancel, button::secondary),
|
||||
],
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.center(Fill)
|
||||
.padding([24, 28])
|
||||
.into()
|
||||
|
|
@ -971,55 +837,6 @@ fn unsaved_changes_dialog_window(name: &str) -> Element<'static, Message> {
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn file_in_use_dialog_window(path: &str, error: &str) -> Element<'static, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COL: Color = Color {
|
||||
r: 0.38,
|
||||
g: 0.38,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COL: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM_COL: Color = Color {
|
||||
r: 0.62,
|
||||
g: 0.62,
|
||||
b: 0.66,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_PRIMARY: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_PRIMARY_HOVER: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.55,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_SECONDARY: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_SECONDARY_HOVER: Color = Color {
|
||||
r: 0.36,
|
||||
g: 0.36,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let file_name = std::path::Path::new(path)
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
|
|
@ -1027,70 +844,45 @@ fn file_in_use_dialog_window(path: &str, error: &str) -> Element<'static, Messag
|
|||
let heading = format!("\"{file_name}\" could not be saved.");
|
||||
let path_line = format!("Path: {path}");
|
||||
let details = format!("Details: {error}");
|
||||
let btn = |label: &'static str, msg: Message, base: Color, hover: Color| {
|
||||
button(text(label).size(13).color(TEXT_COL))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => hover,
|
||||
_ => base,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border {
|
||||
color: BORDER_COL,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([6, 18])
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
text(heading).size(14).color(TEXT_COL),
|
||||
text(heading).size(14),
|
||||
Space::new().height(8),
|
||||
text(
|
||||
"The file is open or being used by another application. \
|
||||
Close it there and retry, or save this drawing under a different name."
|
||||
)
|
||||
.size(13)
|
||||
.color(TEXT_COL)
|
||||
.width(Fill),
|
||||
Space::new().height(12),
|
||||
text(path_line).size(11).color(DIM_COL).width(Fill),
|
||||
text(path_line).size(11).style(dialog_muted_text_style).width(Fill),
|
||||
Space::new().height(4),
|
||||
text(details).size(11).color(DIM_COL).width(Fill),
|
||||
text(details).size(11).style(dialog_muted_text_style).width(Fill),
|
||||
Space::new().height(18),
|
||||
row![
|
||||
btn(
|
||||
dialog_button(
|
||||
"Retry",
|
||||
Message::SaveFileInUseRetry,
|
||||
BTN_PRIMARY,
|
||||
BTN_PRIMARY_HOVER
|
||||
button::primary
|
||||
),
|
||||
Space::new().width(8),
|
||||
btn(
|
||||
dialog_button(
|
||||
"Save As",
|
||||
Message::SaveFileInUseSaveAs,
|
||||
BTN_SECONDARY,
|
||||
BTN_SECONDARY_HOVER
|
||||
button::secondary
|
||||
),
|
||||
Space::new().width(8),
|
||||
btn(
|
||||
dialog_button(
|
||||
"Cancel",
|
||||
Message::SaveFileInUseCancel,
|
||||
BTN_SECONDARY,
|
||||
BTN_SECONDARY_HOVER
|
||||
button::secondary
|
||||
),
|
||||
],
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.padding([18, 20])
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -1099,142 +891,55 @@ fn file_in_use_dialog_window(path: &str, error: &str) -> Element<'static, Messag
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn external_change_dialog_window(path: &str) -> Element<'static, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COL: Color = Color {
|
||||
r: 0.38,
|
||||
g: 0.38,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COL: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM_COL: Color = Color {
|
||||
r: 0.62,
|
||||
g: 0.62,
|
||||
b: 0.66,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_PRIMARY: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_PRIMARY_HOVER: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.55,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_SECONDARY: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_SECONDARY_HOVER: Color = Color {
|
||||
r: 0.36,
|
||||
g: 0.36,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_DANGER: Color = Color {
|
||||
r: 0.62,
|
||||
g: 0.31,
|
||||
b: 0.14,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_DANGER_HOVER: Color = Color {
|
||||
r: 0.78,
|
||||
g: 0.39,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let file_name = std::path::Path::new(path)
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "Drawing".to_string());
|
||||
let heading = format!("\"{file_name}\" was changed by another application.");
|
||||
let path_line = format!("Path: {path}");
|
||||
let btn = |label: &'static str, msg: Message, base: Color, hover: Color| {
|
||||
button(text(label).size(13).color(TEXT_COL))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => hover,
|
||||
_ => base,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border {
|
||||
color: BORDER_COL,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([6, 14])
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
text(heading).size(14).color(TEXT_COL),
|
||||
text(heading).size(14),
|
||||
Space::new().height(8),
|
||||
text(
|
||||
"Saving now could destroy those external changes. Reload the disk copy, \
|
||||
save your local work elsewhere, or explicitly overwrite it."
|
||||
)
|
||||
.size(13)
|
||||
.color(TEXT_COL)
|
||||
.width(Fill),
|
||||
Space::new().height(12),
|
||||
text(path_line).size(11).color(DIM_COL).width(Fill),
|
||||
text(path_line).size(11).style(dialog_muted_text_style).width(Fill),
|
||||
Space::new().height(18),
|
||||
row![
|
||||
btn(
|
||||
dialog_button(
|
||||
"Reload from Disk",
|
||||
Message::ExternalChangeReload,
|
||||
BTN_PRIMARY,
|
||||
BTN_PRIMARY_HOVER
|
||||
button::primary
|
||||
),
|
||||
Space::new().width(8),
|
||||
btn(
|
||||
dialog_button(
|
||||
"Save As",
|
||||
Message::ExternalChangeSaveAs,
|
||||
BTN_SECONDARY,
|
||||
BTN_SECONDARY_HOVER
|
||||
button::secondary
|
||||
),
|
||||
Space::new().width(8),
|
||||
btn(
|
||||
dialog_button(
|
||||
"Overwrite",
|
||||
Message::ExternalChangeOverwrite,
|
||||
BTN_DANGER,
|
||||
BTN_DANGER_HOVER
|
||||
button::danger
|
||||
),
|
||||
Space::new().width(8),
|
||||
btn(
|
||||
dialog_button(
|
||||
"Cancel",
|
||||
Message::ExternalChangeCancel,
|
||||
BTN_SECONDARY,
|
||||
BTN_SECONDARY_HOVER
|
||||
button::secondary
|
||||
),
|
||||
],
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.padding([18, 20])
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -1246,58 +951,31 @@ fn external_change_dialog_window(path: &str) -> Element<'static, Message> {
|
|||
/// bytes, so saving to a different version or to DXF would drop them. Offers to
|
||||
/// save in the source version (keep them) or proceed (drop them).
|
||||
fn aec_drop_dialog_window(count: usize, target: &str, src_version: &str) -> Element<'static, Message> {
|
||||
const BG: Color = Color { r: 0.18, g: 0.18, b: 0.20, a: 1.0 };
|
||||
const BORDER_COL: Color = Color { r: 0.38, g: 0.38, b: 0.42, a: 1.0 };
|
||||
const TEXT_COL: Color = Color { r: 0.90, g: 0.90, b: 0.90, a: 1.0 };
|
||||
const BTN_SAVE: Color = Color { r: 0.20, g: 0.46, b: 0.80, a: 1.0 };
|
||||
const BTN_HOVER: Color = Color { r: 0.26, g: 0.55, b: 0.92, a: 1.0 };
|
||||
const BTN_DISC: Color = Color { r: 0.28, g: 0.28, b: 0.30, a: 1.0 };
|
||||
const BTN_DHOV: Color = Color { r: 0.36, g: 0.36, b: 0.40, a: 1.0 };
|
||||
|
||||
let body_text = format!(
|
||||
"This drawing contains {count} AEC/Civil objects that \"{target}\" \
|
||||
cannot store, so they will not be saved.\n\n\
|
||||
To keep them, save in the source version ({src_version})."
|
||||
);
|
||||
|
||||
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(label).size(13).color(TEXT_COL))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => hov,
|
||||
_ => base,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border {
|
||||
color: BORDER_COL,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.padding([6, 14])
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
text(body_text).size(13).color(TEXT_COL),
|
||||
text(body_text).size(13),
|
||||
iced::widget::Space::new().height(20),
|
||||
row![
|
||||
btn("Save in source version", Message::AecDropSameVersion, BTN_SAVE, BTN_HOVER),
|
||||
dialog_button(
|
||||
"Save in source version",
|
||||
Message::AecDropSameVersion,
|
||||
button::primary
|
||||
),
|
||||
iced::widget::Space::new().width(8),
|
||||
btn("Save anyway", Message::AecDropProceed, BTN_DISC, BTN_DHOV),
|
||||
dialog_button("Save anyway", Message::AecDropProceed, button::warning),
|
||||
iced::widget::Space::new().width(8),
|
||||
btn("Back", Message::AecDropBack, BTN_DISC, BTN_DHOV),
|
||||
dialog_button("Back", Message::AecDropBack, button::secondary),
|
||||
],
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.center(Fill)
|
||||
.padding([24, 28])
|
||||
.into()
|
||||
|
|
@ -1308,14 +986,6 @@ fn aec_drop_dialog_window(count: usize, target: &str, src_version: &str) -> Elem
|
|||
/// Confirm deleting layer(s) that still have objects on them. "Delete Objects"
|
||||
/// erases them and removes the layers; "Cancel" leaves everything.
|
||||
fn layer_delete_warning_window(names: &[String], count: usize) -> Element<'static, Message> {
|
||||
const BG: Color = Color { r: 0.18, g: 0.18, b: 0.20, a: 1.0 };
|
||||
const BORDER_COL: Color = Color { r: 0.38, g: 0.38, b: 0.42, a: 1.0 };
|
||||
const TEXT_COL: Color = Color { r: 0.90, g: 0.90, b: 0.90, a: 1.0 };
|
||||
const BTN_DEL: Color = Color { r: 0.72, g: 0.26, b: 0.24, a: 1.0 };
|
||||
const BTN_DEL_HOV: Color = Color { r: 0.84, g: 0.32, b: 0.30, a: 1.0 };
|
||||
const BTN_CANCEL: Color = Color { r: 0.28, g: 0.28, b: 0.30, a: 1.0 };
|
||||
const BTN_CANCEL_HOV: Color = Color { r: 0.36, g: 0.36, b: 0.40, a: 1.0 };
|
||||
|
||||
let obj = if count == 1 { "object" } else { "objects" };
|
||||
let subject = if names.len() == 1 {
|
||||
format!("Layer \"{}\"", names[0])
|
||||
|
|
@ -1328,38 +998,23 @@ fn layer_delete_warning_window(names: &[String], count: usize) -> Element<'stati
|
|||
if count == 1 { "that object" } else { "those objects" }
|
||||
);
|
||||
|
||||
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(label).size(13).color(TEXT_COL))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => hov,
|
||||
_ => base,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border { color: BORDER_COL, width: 1.0, radius: 4.0.into() },
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.padding([6, 18])
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
text(body_text).size(13).color(TEXT_COL),
|
||||
text(body_text).size(13),
|
||||
iced::widget::Space::new().height(20),
|
||||
row![
|
||||
btn("Delete Objects", Message::LayerDeleteConfirm, BTN_DEL, BTN_DEL_HOV),
|
||||
dialog_button(
|
||||
"Delete Objects",
|
||||
Message::LayerDeleteConfirm,
|
||||
button::danger
|
||||
),
|
||||
iced::widget::Space::new().width(8),
|
||||
btn("Cancel", Message::CloseModal, BTN_CANCEL, BTN_CANCEL_HOV),
|
||||
dialog_button("Cancel", Message::CloseModal, button::secondary),
|
||||
],
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.center(Fill)
|
||||
.padding([24, 28])
|
||||
.into()
|
||||
|
|
@ -1370,99 +1025,30 @@ fn layer_delete_warning_window(names: &[String], count: usize) -> Element<'stati
|
|||
/// just dismisses. Either answer flips the persisted `default_assoc_prompted`
|
||||
/// flag so the dialog never reappears.
|
||||
fn default_assoc_dialog_window() -> Element<'static, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COL: Color = Color {
|
||||
r: 0.38,
|
||||
g: 0.38,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COL: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM_COL: Color = Color {
|
||||
r: 0.62,
|
||||
g: 0.62,
|
||||
b: 0.66,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_YES: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_YHOV: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.55,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_NO: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_NHOV: Color = Color {
|
||||
r: 0.36,
|
||||
g: 0.36,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let btn = |label: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(label).size(13).color(TEXT_COL))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => hov,
|
||||
_ => base,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border {
|
||||
color: BORDER_COL,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.padding([6, 18])
|
||||
};
|
||||
|
||||
container(
|
||||
column![
|
||||
text("Make Open CAD Studio your default CAD app?")
|
||||
.size(15)
|
||||
.color(TEXT_COL),
|
||||
.size(15),
|
||||
iced::widget::Space::new().height(10),
|
||||
text("Open .dwg and .dxf drawings in Open CAD Studio by default. You can change this later in your system settings.")
|
||||
.size(12)
|
||||
.color(DIM_COL),
|
||||
.style(dialog_muted_text_style),
|
||||
iced::widget::Space::new().height(22),
|
||||
row![
|
||||
iced::widget::Space::new().width(Fill),
|
||||
btn("Not now", Message::AssocPromptNo, BTN_NO, BTN_NHOV),
|
||||
dialog_button("Not now", Message::AssocPromptNo, button::secondary),
|
||||
iced::widget::Space::new().width(8),
|
||||
btn("Yes, set as default", Message::AssocPromptYes, BTN_YES, BTN_YHOV),
|
||||
dialog_button(
|
||||
"Yes, set as default",
|
||||
Message::AssocPromptYes,
|
||||
button::primary
|
||||
),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(0),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(dialog_body_style)
|
||||
.center(Fill)
|
||||
.padding([24, 28])
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -26,19 +26,6 @@ pub(super) fn text_inline_overlay(
|
|||
ed: &super::super::text_inline::TextInlineState,
|
||||
canvas: (f32, f32),
|
||||
) -> Element<'_, Message> {
|
||||
const PANEL_BG: Color = Color {
|
||||
r: 0.16,
|
||||
g: 0.16,
|
||||
b: 0.16,
|
||||
a: 0.98,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.40,
|
||||
g: 0.40,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let field = text_input("Text", &ed.value)
|
||||
.id(iced::widget::Id::new(TEXT_INLINE_ID))
|
||||
.on_input(Message::TextInlineInput)
|
||||
|
|
@ -48,14 +35,17 @@ pub(super) fn text_inline_overlay(
|
|||
.width(iced::Length::Fixed(240.0));
|
||||
|
||||
let panel = container(field)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(4);
|
||||
|
||||
|
|
@ -195,7 +185,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
|||
&self,
|
||||
_state: &MTextPreviewState,
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: iced::Rectangle,
|
||||
_cursor: iced::mouse::Cursor,
|
||||
) -> Vec<iced::widget::canvas::Geometry> {
|
||||
|
|
@ -221,12 +211,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
|||
);
|
||||
frame.fill(
|
||||
&rect,
|
||||
Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 0.45,
|
||||
},
|
||||
theme.extended_palette().primary.base.color.scale_alpha(0.45),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -262,12 +247,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
|||
frame.stroke(
|
||||
&path,
|
||||
Stroke::default()
|
||||
.with_color(Color {
|
||||
r: 0.95,
|
||||
g: 0.95,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
})
|
||||
.with_color(theme.extended_palette().warning.base.color)
|
||||
.with_width(1.5),
|
||||
);
|
||||
} else if collapsed {
|
||||
|
|
@ -291,12 +271,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
|||
frame.stroke(
|
||||
&path,
|
||||
Stroke::default()
|
||||
.with_color(Color {
|
||||
r: 0.95,
|
||||
g: 0.95,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
})
|
||||
.with_color(theme.extended_palette().warning.base.color)
|
||||
.with_width(1.5),
|
||||
);
|
||||
}
|
||||
|
|
@ -344,59 +319,34 @@ pub(super) fn mtext_editor_overlay<'a>(
|
|||
modal_resize: iced::Vector,
|
||||
) -> Element<'a, Message> {
|
||||
use super::super::mtext_editor::{JustifyChoice, MTextFmt, ParaAlign};
|
||||
use iced::widget::{canvas, svg};
|
||||
use iced::widget::canvas;
|
||||
|
||||
const BORDER: Color = Color {
|
||||
r: 0.40,
|
||||
g: 0.40,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COL: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const FIELD_BG: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let btn_style = |_: &Theme, status: button::Status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.28,
|
||||
g: 0.40,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
let btn_style = |theme: &Theme, status: button::Status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.strong,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
}
|
||||
};
|
||||
let icon_btn = move |bytes: &'static [u8], msg: Message| -> Element<'static, Message> {
|
||||
button(svg(svg::Handle::from_memory(bytes)).width(18).height(18))
|
||||
button(crate::ui::icons::themed(bytes, 18.0))
|
||||
.on_press(msg)
|
||||
.padding(3)
|
||||
.style(btn_style)
|
||||
.into()
|
||||
};
|
||||
let lbl = |s: &'static str| text(s).size(11).color(TEXT_COL);
|
||||
let lbl = |s: &'static str| text(s).size(11);
|
||||
let small_input = |placeholder: &'static str,
|
||||
val: &str,
|
||||
on: fn(String) -> Message,
|
||||
|
|
@ -640,14 +590,17 @@ pub(super) fn mtext_editor_overlay<'a>(
|
|||
.width(Fill)
|
||||
.height(Fill),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(FIELD_BG)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(2)
|
||||
.width(Fill)
|
||||
|
|
@ -665,8 +618,10 @@ pub(super) fn mtext_editor_overlay<'a>(
|
|||
]
|
||||
.align_y(iced::Alignment::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(crate::ui::style::style_manager::TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -710,50 +665,10 @@ pub(super) fn viewport_context_menu_overlay(
|
|||
last_cmds: Vec<String>,
|
||||
draworder_open: bool,
|
||||
) -> Element<'static, Message> {
|
||||
const MENU_BG: Color = Color {
|
||||
r: 0.17,
|
||||
g: 0.17,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const MENU_BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const ITEM_HOVER: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COL: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const SEP_COL: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let item = |label: String, msg: Message| -> Element<'static, Message> {
|
||||
button(text(label).size(12).color(TEXT_COL))
|
||||
button(text(label).size(12))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ITEM_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([4, 12])
|
||||
.width(Fill)
|
||||
.into()
|
||||
|
|
@ -761,8 +676,10 @@ pub(super) fn viewport_context_menu_overlay(
|
|||
|
||||
let sep = || -> Element<'static, Message> {
|
||||
container(iced::widget::Space::new().width(Fill).height(1))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(SEP_COL)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -773,18 +690,9 @@ pub(super) fn viewport_context_menu_overlay(
|
|||
|
||||
// Indented variant for sub-menu rows (e.g. Draw Order children).
|
||||
let subitem = |label: String, msg: Message| -> Element<'static, Message> {
|
||||
button(text(label).size(12).color(TEXT_COL))
|
||||
button(text(label).size(12))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ITEM_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding(iced::Padding {
|
||||
top: 4.0,
|
||||
right: 12.0,
|
||||
|
|
@ -827,30 +735,21 @@ pub(super) fn viewport_context_menu_overlay(
|
|||
));
|
||||
items.push(sep());
|
||||
let do_caret = if draworder_open {
|
||||
crate::ui::icons::arrow_down(9.0, TEXT_COL)
|
||||
crate::ui::icons::themed_arrow_down(9.0)
|
||||
} else {
|
||||
crate::ui::icons::arrow_right(9.0, TEXT_COL)
|
||||
crate::ui::icons::themed_arrow_right(9.0)
|
||||
};
|
||||
items.push(
|
||||
button(
|
||||
row![
|
||||
text("Draw Order").size(12).color(TEXT_COL),
|
||||
text("Draw Order").size(12),
|
||||
iced::widget::Space::new().width(Fill),
|
||||
do_caret,
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::DrawOrderSubmenuToggle)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ITEM_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([4, 12])
|
||||
.width(Fill)
|
||||
.into(),
|
||||
|
|
@ -909,15 +808,7 @@ pub(super) fn viewport_context_menu_overlay(
|
|||
let menu_col = column(items).spacing(0).width(iced::Length::Fixed(180.0));
|
||||
|
||||
let menu = container(menu_col)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(MENU_BG)),
|
||||
border: Border {
|
||||
color: MENU_BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 0])
|
||||
.width(iced::Length::Fixed(180.0));
|
||||
|
||||
|
|
@ -928,18 +819,12 @@ pub(super) fn viewport_context_menu_overlay(
|
|||
/// snap ICONS only — the names show as hover tooltips. Picking one applies
|
||||
/// that snap to just the next point pick.
|
||||
pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Message> {
|
||||
const PANEL_BG: Color = Color { r: 0.16, g: 0.16, b: 0.16, a: 0.98 };
|
||||
const PANEL_BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const ICON_COLOR: Color = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
|
||||
const HOVER: Color = Color { r: 0.25, g: 0.45, b: 0.70, a: 1.0 };
|
||||
const TIP_BG: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 0.98 };
|
||||
const COLS: usize = 4;
|
||||
|
||||
let cell = |snap_type: crate::snap::SnapType, label: &'static str| -> Element<'static, Message> {
|
||||
let icon = container(crate::ui::icons::tinted::<Message>(
|
||||
let icon = container(crate::ui::icons::themed::<Message>(
|
||||
crate::ui::icons::osnap(snap_type),
|
||||
16.0,
|
||||
ICON_COLOR,
|
||||
))
|
||||
.width(26)
|
||||
.height(26)
|
||||
|
|
@ -947,26 +832,33 @@ pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Messag
|
|||
.align_y(iced::Center);
|
||||
let btn = button(icon)
|
||||
.on_press(Message::SnapOverridePick(snap_type))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
)
|
||||
.then_some(Background::Color(
|
||||
theme.extended_palette().primary.weak.color
|
||||
)),
|
||||
border: Border::default(),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(2);
|
||||
iced::widget::tooltip(
|
||||
btn,
|
||||
container(text(label).size(11).color(Color::WHITE))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TIP_BG)),
|
||||
container(text(label).size(11))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([2, 6]),
|
||||
iced::widget::tooltip::Position::Bottom,
|
||||
|
|
@ -984,14 +876,17 @@ pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Messag
|
|||
}
|
||||
|
||||
let panel = container(grid)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(4);
|
||||
|
||||
|
|
@ -1024,49 +919,6 @@ pub(super) fn qselect_overlay<'a>(
|
|||
properties: &[(String, String)],
|
||||
) -> Element<'a, Message> {
|
||||
use iced::widget::{checkbox, pick_list};
|
||||
const BG: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 0.98,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_OK: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.42,
|
||||
b: 0.68,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_OK_HOV: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.52,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_BG: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_HOV: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let mut type_options: Vec<String> = vec![QSELECT_ANY_TYPE.to_string()];
|
||||
type_options.extend(types.iter().cloned());
|
||||
|
||||
|
|
@ -1111,28 +963,33 @@ pub(super) fn qselect_overlay<'a>(
|
|||
let label = |s: &'static str| {
|
||||
text(s)
|
||||
.size(12)
|
||||
.color(TEXT)
|
||||
.width(iced::Length::Fixed(90.0))
|
||||
};
|
||||
|
||||
let btn = |lbl: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(lbl).size(12).color(TEXT))
|
||||
let btn = |lbl: &'static str, msg: Message, primary: bool| {
|
||||
button(text(lbl).size(12))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(
|
||||
if matches!(st, button::Status::Hovered | button::Status::Pressed) {
|
||||
hov
|
||||
} else {
|
||||
base
|
||||
},
|
||||
)),
|
||||
text_color: TEXT,
|
||||
.style(move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (
|
||||
primary,
|
||||
matches!(st, button::Status::Hovered | button::Status::Pressed),
|
||||
) {
|
||||
(true, true) => palette.primary.strong,
|
||||
(true, false) => palette.primary.base,
|
||||
(false, true) => palette.background.strong,
|
||||
(false, false) => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 14])
|
||||
};
|
||||
|
|
@ -1143,7 +1000,7 @@ pub(super) fn qselect_overlay<'a>(
|
|||
}
|
||||
|
||||
let panel_body = column![
|
||||
text("Quick Select").size(14).color(TEXT),
|
||||
text("Quick Select").size(14),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
label("Object type:"),
|
||||
|
|
@ -1198,15 +1055,15 @@ pub(super) fn qselect_overlay<'a>(
|
|||
.on_toggle(Message::QSelectSetAppend)
|
||||
.size(14),
|
||||
Space::new().width(6),
|
||||
text("Append to current selection").size(12).color(TEXT),
|
||||
text("Append to current selection").size(12),
|
||||
]
|
||||
.align_y(iced::Alignment::Center),
|
||||
Space::new().height(14),
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
btn("Cancel", Message::QSelectClose, BTN_BG, BTN_HOV),
|
||||
btn("Cancel", Message::QSelectClose, false),
|
||||
Space::new().width(8),
|
||||
btn("Apply", Message::QSelectApply, BTN_OK, BTN_OK_HOV),
|
||||
btn("Apply", Message::QSelectApply, true),
|
||||
]
|
||||
.align_y(iced::Alignment::Center),
|
||||
]
|
||||
|
|
@ -1215,14 +1072,17 @@ pub(super) fn qselect_overlay<'a>(
|
|||
let panel = container(panel_body)
|
||||
.padding(16)
|
||||
.width(iced::Length::Fixed(400.0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
// Outside-click catcher — fills the whole screen, sits below the
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::scene::{VIEWCUBE_PX, VIEWCUBE_REGION_PX};
|
|||
use iced::widget::{
|
||||
button, container, mouse_area, pick_list, stack, Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Theme};
|
||||
use iced::{Background, Border, Element, Theme};
|
||||
|
||||
// ── Render-mode picker ──────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -30,16 +30,14 @@ fn vc_btn<'a>(content: Element<'a, Message>, size: f32, msg: Message) -> Element
|
|||
)
|
||||
.padding(0)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| iced::widget::button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
iced::widget::button::Status::Hovered | iced::widget::button::Status::Pressed => Color {
|
||||
r: 0.45,
|
||||
g: 0.62,
|
||||
b: 0.95,
|
||||
a: 0.30,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| iced::widget::button::Style {
|
||||
background: matches!(
|
||||
status,
|
||||
iced::widget::button::Status::Hovered | iced::widget::button::Status::Pressed
|
||||
)
|
||||
.then_some(Background::Color(
|
||||
theme.extended_palette().primary.weak.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
|
|
@ -54,12 +52,6 @@ fn vc_btn<'a>(content: Element<'a, Message>, size: f32, msg: Message) -> Element
|
|||
pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
||||
use crate::scene::NudgeDir;
|
||||
use crate::ui::icons;
|
||||
let tint = Color {
|
||||
r: 0.86,
|
||||
g: 0.89,
|
||||
b: 0.96,
|
||||
a: 1.0,
|
||||
};
|
||||
let r = VIEWCUBE_REGION_PX;
|
||||
let c = r * 0.5;
|
||||
let cube_half = VIEWCUBE_PX as f32 * 0.36; // VIEWCUBE_PX * VIEWCUBE_SCALE
|
||||
|
|
@ -94,23 +86,23 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
|||
vc_place(
|
||||
3.0,
|
||||
3.0,
|
||||
vc_btn(icons::home(13.0, tint), BTN, Message::ViewCubeHome)
|
||||
vc_btn(icons::themed_home(13.0), BTN, Message::ViewCubeHome)
|
||||
),
|
||||
vc_place(
|
||||
rax,
|
||||
ray,
|
||||
vc_btn(icons::undo(12.0, tint), BTN, Message::ViewCubeRoll(false))
|
||||
vc_btn(icons::themed_undo(12.0, true), BTN, Message::ViewCubeRoll(false))
|
||||
),
|
||||
vc_place(
|
||||
rbx,
|
||||
rby,
|
||||
vc_btn(icons::redo(12.0, tint), BTN, Message::ViewCubeRoll(true))
|
||||
vc_btn(icons::themed_redo(12.0, true), BTN, Message::ViewCubeRoll(true))
|
||||
),
|
||||
vc_place(
|
||||
tux,
|
||||
tuy,
|
||||
vc_btn(
|
||||
icons::arrow_down(8.0, tint),
|
||||
icons::themed_arrow_down(8.0),
|
||||
TRI,
|
||||
Message::ViewCubeNudge(NudgeDir::Up)
|
||||
)
|
||||
|
|
@ -119,7 +111,7 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
|||
tdx,
|
||||
tdy,
|
||||
vc_btn(
|
||||
icons::arrow_up(8.0, tint),
|
||||
icons::themed_arrow_up(8.0),
|
||||
TRI,
|
||||
Message::ViewCubeNudge(NudgeDir::Down)
|
||||
)
|
||||
|
|
@ -128,7 +120,7 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
|||
tlx,
|
||||
tly,
|
||||
vc_btn(
|
||||
icons::arrow_right(8.0, tint),
|
||||
icons::themed_arrow_right(8.0),
|
||||
TRI,
|
||||
Message::ViewCubeNudge(NudgeDir::Left)
|
||||
)
|
||||
|
|
@ -137,7 +129,7 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
|||
trx,
|
||||
try_,
|
||||
vc_btn(
|
||||
icons::arrow_left(8.0, tint),
|
||||
icons::themed_arrow_left(8.0),
|
||||
TRI,
|
||||
Message::ViewCubeNudge(NudgeDir::Right)
|
||||
)
|
||||
|
|
@ -155,12 +147,6 @@ pub(super) const UCS_PICKER_W: f32 = 84.0;
|
|||
|
||||
/// The WCS / named-UCS selector shown under the cube.
|
||||
pub(super) fn viewcube_ucs_picker<'a>(current: String, names: Vec<String>) -> Element<'a, Message> {
|
||||
let light = Color {
|
||||
r: 0.85,
|
||||
g: 0.87,
|
||||
b: 0.93,
|
||||
a: 1.0,
|
||||
};
|
||||
let mut options = vec!["WCS".to_string()];
|
||||
options.extend(names);
|
||||
let selected = if current.is_empty() {
|
||||
|
|
@ -174,20 +160,20 @@ pub(super) fn viewcube_ucs_picker<'a>(current: String, names: Vec<String>) -> El
|
|||
// Fixed width so the caller can centre it under the cube centre with a
|
||||
// simple half-width offset (content-sized width would drift off-centre).
|
||||
.width(iced::Length::Fixed(UCS_PICKER_W))
|
||||
.style(move |_: &Theme, _| iced::widget::pick_list::Style {
|
||||
background: Background::Color(Color {
|
||||
r: 0.16,
|
||||
g: 0.17,
|
||||
b: 0.20,
|
||||
a: 0.92,
|
||||
}),
|
||||
.style(move |theme: &Theme, _| {
|
||||
let palette = theme.extended_palette();
|
||||
iced::widget::pick_list::Style {
|
||||
background: Background::Color(palette.background.weak.color),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
text_color: light,
|
||||
placeholder_color: light,
|
||||
handle_color: light,
|
||||
text_color: palette.background.base.text,
|
||||
placeholder_color: palette.background.base.text.scale_alpha(0.68),
|
||||
handle_color: palette.background.base.text,
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,25 +20,6 @@ pub struct ColorExtras {
|
|||
pub by_block: bool,
|
||||
}
|
||||
|
||||
const PICKER_BG: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
/// Encode a colour as the ACI integer string the style editors store
|
||||
/// (ByBlock=0, ByLayer=256, indexed 1-255; RGB has no ACI slot → ByLayer).
|
||||
pub fn color_to_aci_string(c: AcadColor) -> String {
|
||||
|
|
@ -85,15 +66,10 @@ pub fn color_display_name(c: AcadColor) -> String {
|
|||
/// A small colour square.
|
||||
fn swatch<'a>(bg: Color) -> Element<'a, Message> {
|
||||
container(text("").width(13).height(13))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.5,
|
||||
},
|
||||
color: theme.extended_palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
|
|
@ -126,8 +102,8 @@ pub fn color_selector<'a>(
|
|||
let head = button(
|
||||
row![
|
||||
swatch(cur_bg),
|
||||
text(cur_name).size(11).color(TEXT),
|
||||
crate::ui::icons::arrow_toggle(open, 9.0, TEXT),
|
||||
text(cur_name).size(11),
|
||||
crate::ui::icons::themed_arrow_toggle(open, 9.0),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -141,14 +117,17 @@ pub fn color_selector<'a>(
|
|||
}
|
||||
|
||||
let popup = container(color_list(extras, on_select, on_more))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PICKER_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(5)
|
||||
.width(220);
|
||||
|
|
@ -172,19 +151,15 @@ pub fn color_list<'a>(
|
|||
let named_row = |color: AcadColor| -> Element<'a, Message> {
|
||||
let (bg, name) = acad_color_display(color);
|
||||
button(
|
||||
row![swatch(bg), text(name).size(11).color(TEXT)]
|
||||
row![swatch(bg), text(name).size(11)]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(on_select(color))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered)
|
||||
.then_some(Background::Color(Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(theme.extended_palette().background.strong.color)
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 4])
|
||||
|
|
@ -203,16 +178,12 @@ pub fn color_list<'a>(
|
|||
list = list.push(named_row(AcadColor::Index(i)));
|
||||
}
|
||||
list = list.push(
|
||||
button(text("More…").size(11).color(TEXT))
|
||||
button(text("More…").size(11))
|
||||
.on_press(on_more)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered)
|
||||
.then_some(Background::Color(Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(theme.extended_palette().background.strong.color)
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 4])
|
||||
|
|
@ -227,7 +198,7 @@ pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'sta
|
|||
let chip = |color: AcadColor, label: &'static str| -> Element<'static, Message> {
|
||||
let (bg, _) = acad_color_display(color);
|
||||
button(
|
||||
row![swatch(bg), text(label).size(11).color(TEXT)]
|
||||
row![swatch(bg), text(label).size(11)]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
|
|
@ -250,18 +221,13 @@ pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'sta
|
|||
r = r.push(
|
||||
button(text("").width(18).height(18))
|
||||
.on_press(on_pick(AcadColor::Index(ci)))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
.style(move |theme: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: if matches!(status, button::Status::Hovered) {
|
||||
Color::WHITE
|
||||
theme.extended_palette().primary.base.color
|
||||
} else {
|
||||
Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.4,
|
||||
}
|
||||
theme.extended_palette().background.neutral.color
|
||||
},
|
||||
width: if matches!(status, button::Status::Hovered) {
|
||||
1.5
|
||||
|
|
@ -281,14 +247,16 @@ pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'sta
|
|||
|
||||
container(
|
||||
column![
|
||||
text("Select Color").size(13).color(TEXT),
|
||||
text("Select Color").size(13),
|
||||
row![chip(AcadColor::ByLayer, "ByLayer"), chip(AcadColor::ByBlock, "ByBlock")].spacing(6),
|
||||
scrollable(grid).height(Length::Fill),
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PICKER_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(10)
|
||||
|
|
|
|||
|
|
@ -370,11 +370,14 @@ impl CommandLine {
|
|||
let history_rows = visible[start..]
|
||||
.iter()
|
||||
.fold(column![].spacing(0), |col, entry| {
|
||||
let color = match entry.kind {
|
||||
EntryKind::Command => CMD_COLOR,
|
||||
EntryKind::Output => OUT_COLOR,
|
||||
EntryKind::Error => ERR_COLOR,
|
||||
EntryKind::Info => INFO_COLOR,
|
||||
let kind = entry.kind.clone();
|
||||
let entry_text = |value: String| {
|
||||
text(value).size(11).style({
|
||||
let kind = kind.clone();
|
||||
move |theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(history_color(theme, &kind)),
|
||||
}
|
||||
})
|
||||
};
|
||||
// The current step's prompt is the single pinned line. When the
|
||||
// step offers options, render them as clickable buttons inline
|
||||
|
|
@ -384,31 +387,28 @@ impl CommandLine {
|
|||
// listing is dropped here (the history log keeps the full text).
|
||||
if entry.pinned && !self.step_options.is_empty() {
|
||||
let shown = strip_option_listing(&entry.text);
|
||||
let mut r = row![text(shown).size(11).color(color)]
|
||||
let mut r = row![entry_text(shown)]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
for opt in &self.step_options {
|
||||
let btn = button(
|
||||
text(opt.label.to_uppercase()).size(11).color(CMD_COLOR),
|
||||
)
|
||||
let btn = button(text(opt.label.to_uppercase()).size(11))
|
||||
.on_press(Message::CommandOptionPick(opt.keyword.clone()))
|
||||
.padding([1, 6])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered) {
|
||||
Color {
|
||||
r: 0.28,
|
||||
g: 0.40,
|
||||
b: 0.56,
|
||||
a: 1.0,
|
||||
}
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
) {
|
||||
palette.primary.weak
|
||||
} else {
|
||||
INPUT_ROW_BG
|
||||
palette.background.weakest
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
|
|
@ -419,46 +419,41 @@ impl CommandLine {
|
|||
}
|
||||
col.push(container(r).padding([1, 8]))
|
||||
} else {
|
||||
col.push(container(text(&entry.text).size(11).color(color)).padding([1, 8]))
|
||||
col.push(container(entry_text(entry.text.clone())).padding([1, 8]))
|
||||
}
|
||||
});
|
||||
let prompt = container(text("Command:").size(11).color(PROMPT_COLOR)).padding([5, 8]);
|
||||
let prompt = container(
|
||||
text("Command:").size(11).style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().success.base.color),
|
||||
}),
|
||||
)
|
||||
.padding([5, 8]);
|
||||
// Literal-space toggle: while active, every line behaves as if it
|
||||
// started with `>` — Space stays in the line instead of submitting, so
|
||||
// arguments with spaces (text strings, paths, `UCS Z 90` as one line)
|
||||
// can be typed. Persists until toggled off; a hand-typed leading `>`
|
||||
// lights the button too (same mode, one line only).
|
||||
let literal_active = self.literal_spaces || self.input.starts_with('>');
|
||||
let literal_btn = button(text(">").size(11).color(if literal_active {
|
||||
Color::WHITE
|
||||
} else {
|
||||
PROMPT_COLOR
|
||||
}))
|
||||
let literal_btn = button(text(">").size(11))
|
||||
.on_press(Message::CommandLiteralToggle)
|
||||
.padding([2, 6])
|
||||
.style(move |_: &Theme, status| {
|
||||
let bg = if literal_active {
|
||||
Color {
|
||||
r: 0.28,
|
||||
g: 0.40,
|
||||
b: 0.56,
|
||||
a: 1.0,
|
||||
}
|
||||
} else if matches!(status, button::Status::Hovered) {
|
||||
Color {
|
||||
r: 0.22,
|
||||
g: 0.30,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
}
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if literal_active {
|
||||
palette.primary.weak
|
||||
} else if matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
) {
|
||||
palette.background.weak
|
||||
} else {
|
||||
INPUT_ROW_BG
|
||||
palette.background.weakest
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
|
|
@ -467,19 +462,10 @@ impl CommandLine {
|
|||
});
|
||||
let literal_tip = container(
|
||||
text("Literal spaces: Space stays in the line instead of running the command (same as typing a leading '>'). Stays on until toggled off.")
|
||||
.size(11)
|
||||
.color(Color::WHITE),
|
||||
.size(11),
|
||||
)
|
||||
.padding([3, 6])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
.style(container::bordered_box);
|
||||
let literal_btn = tooltip(literal_btn, literal_tip, tooltip::Position::Top).gap(4);
|
||||
// While dynamic input is capturing keystrokes, the command-line
|
||||
// text field is left without an `on_input` handler so it can't
|
||||
|
|
@ -492,33 +478,6 @@ impl CommandLine {
|
|||
.on_submit(Message::CommandSubmit);
|
||||
}
|
||||
let input = input
|
||||
.style(|_: &Theme, _| text_input::Style {
|
||||
background: Background::Color(INPUT_BG),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.40,
|
||||
g: 0.60,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: Color {
|
||||
r: 0.4,
|
||||
g: 0.4,
|
||||
b: 0.4,
|
||||
a: 1.0,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.5,
|
||||
},
|
||||
})
|
||||
.size(11)
|
||||
.padding([4, 6]);
|
||||
// Autocomplete suggestions panel, shown above the input row
|
||||
|
|
@ -537,31 +496,25 @@ impl CommandLine {
|
|||
let mut col = column![].spacing(0).width(Length::Fill);
|
||||
for (idx, cmd) in matches.iter().enumerate() {
|
||||
let is_selected = cursor == idx;
|
||||
let row = button(text(cmd.clone()).size(11).color(CMD_COLOR))
|
||||
let row = button(text(cmd.clone()).size(11))
|
||||
.on_press(Message::CommandSuggestionPick(cmd.clone()))
|
||||
.width(Length::Fill)
|
||||
.padding([2, 8])
|
||||
.style(move |_: &Theme, status| {
|
||||
let bg = if is_selected {
|
||||
Color {
|
||||
r: 0.28,
|
||||
g: 0.40,
|
||||
b: 0.56,
|
||||
a: 1.0,
|
||||
}
|
||||
} else if matches!(status, button::Status::Hovered) {
|
||||
Color {
|
||||
r: 0.22,
|
||||
g: 0.30,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
}
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if is_selected {
|
||||
palette.primary.weak
|
||||
} else if matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
) {
|
||||
palette.background.weak
|
||||
} else {
|
||||
PANEL_BG
|
||||
palette.background.base
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border::default(),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
@ -569,15 +522,7 @@ impl CommandLine {
|
|||
col = col.push(row);
|
||||
}
|
||||
container(col)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -590,22 +535,13 @@ impl CommandLine {
|
|||
// user can recover anything that has already faded off the
|
||||
// overlay.
|
||||
let dropdown_icon = if self.history_open {
|
||||
crate::ui::icons::arrow_down(11.0, PROMPT_COLOR)
|
||||
crate::ui::icons::themed_arrow_down(11.0)
|
||||
} else {
|
||||
crate::ui::icons::arrow_right(11.0, PROMPT_COLOR)
|
||||
crate::ui::icons::themed_arrow_right(11.0)
|
||||
};
|
||||
let dropdown_btn = button(dropdown_icon)
|
||||
.on_press(Message::CommandHistoryToggle)
|
||||
.style(|_: &Theme, _status| button::Style {
|
||||
background: Some(Background::Color(INPUT_ROW_BG)),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([2, 6]);
|
||||
let input_row = row![prompt, literal_btn, input, dropdown_btn]
|
||||
.spacing(4)
|
||||
|
|
@ -625,23 +561,21 @@ impl CommandLine {
|
|||
.size(11)
|
||||
.padding([2, 8])
|
||||
.max_height(180.0)
|
||||
.style(|_: &Theme, _status| text_editor::Style {
|
||||
background: Background::Color(PANEL_BG),
|
||||
border: Border::default(),
|
||||
placeholder: OUT_COLOR,
|
||||
value: CMD_COLOR,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.5,
|
||||
},
|
||||
.style(|theme: &Theme, _status| {
|
||||
let palette = theme.extended_palette();
|
||||
text_editor::Style {
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border::default(),
|
||||
placeholder: palette.background.base.text.scale_alpha(0.72),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
});
|
||||
// Header strip: a Copy-all and a Clear button pinned above the log.
|
||||
let copy_btn = button(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::COPY, 11.0, PROMPT_COLOR),
|
||||
text("Copy").size(11).color(CMD_COLOR),
|
||||
crate::ui::icons::themed_success(crate::ui::icons::COPY, 11.0),
|
||||
text("Copy").size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -651,8 +585,8 @@ impl CommandLine {
|
|||
.padding([2, 6]);
|
||||
let clear_btn = button(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::TRASH, 11.0, ERR_COLOR),
|
||||
text("Clear").size(11).color(CMD_COLOR),
|
||||
crate::ui::icons::themed_warning(crate::ui::icons::TRASH, 11.0),
|
||||
text("Clear").size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -668,15 +602,7 @@ impl CommandLine {
|
|||
.width(Length::Fill)
|
||||
.padding([2, 6]);
|
||||
let panel = container(column![header, log])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.width(Length::Fill)
|
||||
.padding([4, 0]);
|
||||
opaque(panel).into()
|
||||
|
|
@ -688,35 +614,43 @@ impl CommandLine {
|
|||
autocomplete,
|
||||
dropdown,
|
||||
container(history_rows)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(HISTORY_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.padding([2, 0]),
|
||||
container(input_row)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(INPUT_ROW_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weakest.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
// Match the drawing tab bar / status bar height, and vertically
|
||||
// centre the prompt/input/dropdown within it (issue #216).
|
||||
.center_y(Length::Fixed(30.0)),
|
||||
])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fixed(720.0))
|
||||
.into()
|
||||
|
|
@ -780,22 +714,18 @@ pub fn ranked_matches(
|
|||
|
||||
/// Flat button style for the history dropdown's Copy / Clear strip: a subtle
|
||||
/// filled pill that brightens on hover.
|
||||
fn header_btn_style(_: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = if matches!(status, button::Status::Hovered) {
|
||||
Color {
|
||||
r: 0.24,
|
||||
g: 0.24,
|
||||
b: 0.24,
|
||||
a: 1.0,
|
||||
}
|
||||
fn header_btn_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
palette.background.weak
|
||||
} else {
|
||||
INPUT_ROW_BG
|
||||
palette.background.weakest
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
|
|
@ -803,66 +733,15 @@ fn header_btn_style(_: &Theme, status: button::Status) -> button::Style {
|
|||
}
|
||||
}
|
||||
|
||||
const PANEL_BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const HISTORY_BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const INPUT_ROW_BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
const INPUT_BG: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COLOR: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const PROMPT_COLOR: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.78,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const CMD_COLOR: Color = Color {
|
||||
r: 0.80,
|
||||
g: 0.80,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
const OUT_COLOR: Color = Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
};
|
||||
const ERR_COLOR: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const INFO_COLOR: Color = Color {
|
||||
r: 0.50,
|
||||
g: 0.70,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
fn history_color(theme: &Theme, kind: &EntryKind) -> Color {
|
||||
let palette = theme.extended_palette();
|
||||
match kind {
|
||||
EntryKind::Command => palette.background.base.text,
|
||||
EntryKind::Output => palette.background.base.text.scale_alpha(0.72),
|
||||
EntryKind::Error => palette.danger.base.color,
|
||||
EntryKind::Info => palette.primary.base.color,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
172
src/ui/icons.rs
172
src/ui/icons.rs
|
|
@ -7,7 +7,7 @@
|
|||
//! boxes. Drawing them from SVG instead makes the chrome font-independent.
|
||||
|
||||
use iced::widget::{container, svg, Space};
|
||||
use iced::{Color, Element, Length, Theme};
|
||||
use iced::{Element, Length, Theme};
|
||||
|
||||
const TRI_DOWN: &[u8] = include_bytes!("../../assets/icons/ui/tri_down.svg");
|
||||
const TRI_UP: &[u8] = include_bytes!("../../assets/icons/ui/tri_up.svg");
|
||||
|
|
@ -65,7 +65,6 @@ pub const FILE_EXPORT: &[u8] = include_bytes!("../../assets/icons/ui/file_export
|
|||
pub const PRINT: &[u8] = include_bytes!("../../assets/icons/ui/print.svg");
|
||||
pub const HEART: &[u8] = include_bytes!("../../assets/icons/ui/heart.svg");
|
||||
pub const DOT: &[u8] = include_bytes!("../../assets/icons/ui/dot.svg");
|
||||
pub const TRI_LEFT_B: &[u8] = include_bytes!("../../assets/icons/ui/tri_left.svg");
|
||||
pub const ARROW_LONG_RIGHT: &[u8] = include_bytes!("../../assets/icons/ui/arrow_long_right.svg");
|
||||
|
||||
// ── Status-bar toggle icons (issue #216) ──────────────────────────────────
|
||||
|
|
@ -82,26 +81,112 @@ pub const ST_FILTER: &[u8] = include_bytes!("../../assets/icons/status/filter.sv
|
|||
pub const ST_SELCYCLE: &[u8] = include_bytes!("../../assets/icons/status/selcycle.svg");
|
||||
pub const ST_CLEANSCREEN: &[u8] = include_bytes!("../../assets/icons/status/cleanscreen.svg");
|
||||
|
||||
/// Render one of the bundled SVGs tinted to `color` at a square `size`.
|
||||
pub fn tinted<'a, M: 'a>(bytes: &'static [u8], size: f32, color: Color) -> Element<'a, M> {
|
||||
/// Render a chrome icon with the active Iced theme's normal text color.
|
||||
pub fn themed<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(move |_: &Theme, _| svg::Style { color: Some(color) })
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(theme.extended_palette().background.base.text),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Backwards-compatible alias used by the caret/undo/redo helpers below.
|
||||
fn icon<'a, M: 'a>(bytes: &'static [u8], size: f32, color: Color) -> Element<'a, M> {
|
||||
tinted(bytes, size, color)
|
||||
/// Render secondary chrome with the active Iced theme's text color.
|
||||
pub fn themed_secondary<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A fixed-width (14 px) "current row" check column: a green-tintable check
|
||||
/// when `active`, otherwise an empty spacer that preserves alignment. Used by
|
||||
/// the many dropdown / popup list rows that mark the selected entry.
|
||||
pub fn check_cell<'a, M: 'a>(active: bool, color: Color) -> Element<'a, M> {
|
||||
/// Render disabled chrome with the active Iced theme's text color.
|
||||
pub fn themed_disabled<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.42),
|
||||
),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Render an emphasized chrome icon with the active Iced theme's primary color.
|
||||
pub fn themed_primary<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Render a positive-state chrome icon with the active Iced theme's success color.
|
||||
pub fn themed_success<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(theme.extended_palette().success.base.color),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Render a warning-state chrome icon from the active Iced theme.
|
||||
pub fn themed_warning<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(theme.extended_palette().warning.base.color),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Render a destructive-state chrome icon from the active Iced theme.
|
||||
pub fn themed_danger<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(theme.extended_palette().danger.base.color),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Render an icon with the foreground chosen for a danger-coloured surface.
|
||||
pub fn themed_danger_text<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||
svg(svg::Handle::from_memory(bytes))
|
||||
.width(size)
|
||||
.height(size)
|
||||
.style(|theme: &Theme, _| svg::Style {
|
||||
color: Some(theme.extended_palette().danger.base.text),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Fixed-width check column colored from the active Iced theme.
|
||||
pub fn themed_check_cell<'a, M: 'a>(active: bool) -> Element<'a, M> {
|
||||
let inner: Element<'a, M> = if active {
|
||||
tinted(CHECK, 11.0, color)
|
||||
themed_primary(CHECK, 11.0)
|
||||
} else {
|
||||
Space::new().width(0).into()
|
||||
};
|
||||
|
|
@ -166,46 +251,59 @@ pub fn layer_lock(locked: bool) -> &'static [u8] {
|
|||
}
|
||||
}
|
||||
|
||||
/// Downward dropdown caret (replaces `▾`).
|
||||
pub fn arrow_down<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_DOWN, size, color)
|
||||
pub fn themed_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed(TRI_DOWN, size)
|
||||
}
|
||||
|
||||
/// Upward dropdown caret, shown when a dropdown is open (replaces `▲`).
|
||||
pub fn arrow_up<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_UP, size, color)
|
||||
pub fn themed_arrow_up<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed(TRI_UP, size)
|
||||
}
|
||||
|
||||
/// Rightward caret for a collapsed item (replaces `▸`).
|
||||
pub fn arrow_right<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_RIGHT, size, color)
|
||||
pub fn themed_arrow_right<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed(TRI_RIGHT, size)
|
||||
}
|
||||
|
||||
/// Leftward caret.
|
||||
pub fn arrow_left<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(TRI_LEFT, size, color)
|
||||
pub fn themed_arrow_left<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed(TRI_LEFT, size)
|
||||
}
|
||||
|
||||
/// House glyph — the ViewCube "home view" button.
|
||||
pub fn home<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(HOME, size, color)
|
||||
pub fn themed_primary_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed_primary(TRI_DOWN, size)
|
||||
}
|
||||
|
||||
pub fn themed_secondary_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed_secondary(TRI_DOWN, size)
|
||||
}
|
||||
|
||||
pub fn themed_disabled_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed_disabled(TRI_DOWN, size)
|
||||
}
|
||||
|
||||
pub fn themed_home<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||
themed(HOME, size)
|
||||
}
|
||||
|
||||
/// Caret that flips up/down with `open`.
|
||||
pub fn arrow_toggle<'a, M: 'a>(open: bool, size: f32, color: Color) -> Element<'a, M> {
|
||||
pub fn themed_arrow_toggle<'a, M: 'a>(open: bool, size: f32) -> Element<'a, M> {
|
||||
if open {
|
||||
arrow_up(size, color)
|
||||
themed_arrow_up(size)
|
||||
} else {
|
||||
arrow_down(size, color)
|
||||
themed_arrow_down(size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Undo curved arrow (replaces `↶`).
|
||||
pub fn undo<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(UNDO, size, color)
|
||||
pub fn themed_undo<'a, M: 'a>(size: f32, enabled: bool) -> Element<'a, M> {
|
||||
if enabled {
|
||||
themed(UNDO, size)
|
||||
} else {
|
||||
themed_disabled(UNDO, size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Redo curved arrow (replaces `↷`).
|
||||
pub fn redo<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
||||
icon(REDO, size, color)
|
||||
pub fn themed_redo<'a, M: 'a>(size: f32, enabled: bool) -> Element<'a, M> {
|
||||
if enabled {
|
||||
themed(REDO, size)
|
||||
} else {
|
||||
themed_disabled(REDO, size)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,34 +7,7 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, mouse_area, opaque, row, stack};
|
||||
use iced::{Background, Border, Color, Element, Length, Padding, Theme, Vector};
|
||||
|
||||
const PANEL: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_C: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
/// Title-bar background.
|
||||
const TITLE_C: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
/// Drag-handle arrow — bright green so the move affordance stands out.
|
||||
const GRIP_C: Color = Color {
|
||||
r: 0.2,
|
||||
g: 1.0,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Length, Padding, Theme, Vector};
|
||||
|
||||
/// Stack `content` over `base` behind a dimmed backdrop, framed with a
|
||||
/// draggable title bar (the ✕ close button at its right end). The backdrop only
|
||||
|
|
@ -52,15 +25,9 @@ pub fn modal<'a>(
|
|||
offset: Vector,
|
||||
resizable: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let close = button(crate::ui::icons::tinted(
|
||||
let close = button(crate::ui::icons::themed_danger(
|
||||
crate::ui::icons::CLOSE,
|
||||
13.0,
|
||||
Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
},
|
||||
))
|
||||
.on_press(on_close)
|
||||
.padding([1, 7])
|
||||
|
|
@ -71,10 +38,12 @@ pub fn modal<'a>(
|
|||
// screen width — the dialog stays sized to its content. Pressing the grip
|
||||
// starts a drag (handled in `update`).
|
||||
let grip = mouse_area(
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::MOVE, 14.0, GRIP_C))
|
||||
container(crate::ui::icons::themed_primary(crate::ui::icons::MOVE, 14.0))
|
||||
.padding([1, 7])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TITLE_C)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
|
|
@ -89,12 +58,7 @@ pub fn modal<'a>(
|
|||
// overlaid at the right edge. The bar takes an explicit `title_width`
|
||||
// (the caller's content width) instead of `Fill` — a Fill child inside
|
||||
// the Shrink frame would blow the dialog out to the full screen.
|
||||
let title_text = iced::widget::text(title).size(15).color(Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
});
|
||||
let title_text = iced::widget::text(title).size(15);
|
||||
let title_bar = stack![
|
||||
container(title_text)
|
||||
.width(Length::Fixed(title_width))
|
||||
|
|
@ -108,10 +72,12 @@ pub fn modal<'a>(
|
|||
.align_y(iced::alignment::Vertical::Center),
|
||||
];
|
||||
|
||||
let panel_style = |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL)),
|
||||
let panel_style = |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
border: Border {
|
||||
color: BORDER_C,
|
||||
color: theme.extended_palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
|
|
@ -125,7 +91,7 @@ pub fn modal<'a>(
|
|||
// top-right above the content either way (`align_x(Right)`).
|
||||
let body = if resizable {
|
||||
let resize = mouse_area(
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::RESIZE, 15.0, GRIP_C))
|
||||
container(crate::ui::icons::themed_primary(crate::ui::icons::RESIZE, 15.0))
|
||||
.padding([0, 2]),
|
||||
)
|
||||
.on_press(Message::ModalResizeGrab)
|
||||
|
|
@ -157,11 +123,15 @@ pub fn modal<'a>(
|
|||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.padding(pad)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.55,
|
||||
..Color::BLACK
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.strongest
|
||||
.color
|
||||
.scale_alpha(0.55),
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
|
|
@ -177,24 +147,15 @@ pub fn modal<'a>(
|
|||
.into()
|
||||
}
|
||||
|
||||
fn close_style(_: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.7,
|
||||
g: 0.2,
|
||||
b: 0.2,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
},
|
||||
fn close_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.danger.strong,
|
||||
_ => palette.background.weakest,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ struct SelectionCanvas {
|
|||
hover_locked: bool,
|
||||
}
|
||||
|
||||
fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker) {
|
||||
fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker, theme: &Theme) {
|
||||
let sp = grip.pos;
|
||||
let h = crate::scene::pick::grip::GRIP_HALF_PX;
|
||||
let path = match grip.shape {
|
||||
|
|
@ -332,30 +332,13 @@ fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker) {
|
|||
};
|
||||
|
||||
if grip.is_hot {
|
||||
frame.fill(
|
||||
&path,
|
||||
Color {
|
||||
r: 1.0,
|
||||
g: 0.15,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
},
|
||||
);
|
||||
frame.fill(&path, theme.extended_palette().danger.base.color);
|
||||
} else {
|
||||
let color = Color {
|
||||
r: 0.10,
|
||||
g: 0.45,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
let palette = theme.extended_palette();
|
||||
let color = palette.primary.base.color;
|
||||
frame.fill(
|
||||
&path,
|
||||
Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.20,
|
||||
a: 0.7,
|
||||
},
|
||||
palette.background.base.color.scale_alpha(0.7),
|
||||
);
|
||||
frame.stroke(
|
||||
&path,
|
||||
|
|
@ -442,7 +425,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
&self,
|
||||
_state: &(),
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: iced::Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
|
|
@ -452,18 +435,13 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
// Filled bars in the pane_grid spacing gaps, so adjacent panes read as
|
||||
// distinct viewports. Drawn first so all other overlays sit on top.
|
||||
if !self.dividers.is_empty() {
|
||||
const DIVIDER: Color = Color {
|
||||
r: 0.46,
|
||||
g: 0.52,
|
||||
b: 0.62,
|
||||
a: 1.0,
|
||||
};
|
||||
let divider = theme.extended_palette().background.neutral.color;
|
||||
for d in &self.dividers {
|
||||
let bar = canvas::Path::rectangle(
|
||||
Point::new(d.x, d.y),
|
||||
iced::Size::new(d.width.max(1.0), d.height.max(1.0)),
|
||||
);
|
||||
frame.fill(&bar, DIVIDER);
|
||||
frame.fill(&bar, divider);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,23 +450,13 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
// under the cursor, and drag a translucent ghost card along the cursor
|
||||
// so the pane is visibly "moving".
|
||||
if let Some(src) = self.pane_move_rect {
|
||||
let accent = Color {
|
||||
r: 0.30,
|
||||
g: 0.62,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
};
|
||||
let accent = theme.extended_palette().primary.base.color;
|
||||
// Source pane: dimmed + dashed-feel outline (it has been lifted).
|
||||
let src_path =
|
||||
canvas::Path::rectangle(Point::new(src.x, src.y), iced::Size::new(src.width, src.height));
|
||||
frame.fill(
|
||||
&src_path,
|
||||
Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.28,
|
||||
},
|
||||
theme.extended_palette().background.strong.color.scale_alpha(0.28),
|
||||
);
|
||||
frame.stroke(
|
||||
&src_path,
|
||||
|
|
@ -539,18 +507,20 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
// Draw a selection marquee (green crossing / blue window) as a filled,
|
||||
// stroked rectangle between two canvas points. Shared by the live
|
||||
// box-selection and the preview-only window marquee (#291).
|
||||
fn draw_marquee(frame: &mut canvas::Frame, a: Point, b: Point, crossing: bool) {
|
||||
let (fill, stroke) = if crossing {
|
||||
(
|
||||
Color { r: 0.20, g: 0.72, b: 0.44, a: 0.12 },
|
||||
Color { r: 0.20, g: 0.72, b: 0.44, a: 0.9 },
|
||||
)
|
||||
fn draw_marquee(
|
||||
frame: &mut canvas::Frame,
|
||||
a: Point,
|
||||
b: Point,
|
||||
crossing: bool,
|
||||
theme: &Theme,
|
||||
) {
|
||||
let base = if crossing {
|
||||
theme.extended_palette().success.base.color
|
||||
} else {
|
||||
(
|
||||
Color { r: 0.20, g: 0.44, b: 0.72, a: 0.12 },
|
||||
Color { r: 0.20, g: 0.44, b: 0.72, a: 0.9 },
|
||||
)
|
||||
theme.extended_palette().primary.base.color
|
||||
};
|
||||
let fill = base.scale_alpha(0.12);
|
||||
let stroke = base.scale_alpha(0.9);
|
||||
let x0 = a.x.min(b.x);
|
||||
let y0 = a.y.min(b.y);
|
||||
let w = (a.x - b.x).abs();
|
||||
|
|
@ -568,45 +538,21 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
}
|
||||
|
||||
if let (Some(a), Some(b)) = (self.selection.box_anchor, self.selection.box_current) {
|
||||
draw_marquee(&mut frame, a, b, self.selection.box_crossing);
|
||||
draw_marquee(&mut frame, a, b, self.selection.box_crossing, theme);
|
||||
}
|
||||
// Preview marquee for point-picked windows (STRETCH) — same look, no pick.
|
||||
if let Some((a, b, crossing)) = self.selection.preview_box {
|
||||
draw_marquee(&mut frame, a, b, crossing);
|
||||
draw_marquee(&mut frame, a, b, crossing, theme);
|
||||
}
|
||||
|
||||
if self.selection.poly_active && self.selection.poly_points.len() > 1 {
|
||||
let (fill, stroke) = if self.selection.poly_crossing {
|
||||
(
|
||||
Color {
|
||||
r: 0.20,
|
||||
g: 0.72,
|
||||
b: 0.44,
|
||||
a: 0.12,
|
||||
},
|
||||
Color {
|
||||
r: 0.20,
|
||||
g: 0.72,
|
||||
b: 0.44,
|
||||
a: 0.9,
|
||||
},
|
||||
)
|
||||
let base = if self.selection.poly_crossing {
|
||||
theme.extended_palette().success.base.color
|
||||
} else {
|
||||
(
|
||||
Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.12,
|
||||
},
|
||||
Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.9,
|
||||
},
|
||||
)
|
||||
theme.extended_palette().primary.base.color
|
||||
};
|
||||
let fill = base.scale_alpha(0.12);
|
||||
let stroke = base.scale_alpha(0.9);
|
||||
if let Some(cur) = self.selection.last_move_pos {
|
||||
let start = self.selection.poly_points[0];
|
||||
let fill_path = canvas::Path::new(|p| {
|
||||
|
|
@ -667,7 +613,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
};
|
||||
frame.with_clip(grip_clip, |frame| {
|
||||
for grip in &self.grips {
|
||||
draw_grip_marker(frame, grip);
|
||||
draw_grip_marker(frame, grip, theme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -962,12 +908,12 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
// PAN mode replaces the crosshair with a hand cursor.
|
||||
if !over_viewcube && !over_divider && !self.pan_mode && !self.suppressed {
|
||||
if let Some(cp) = self.selection.last_move_pos {
|
||||
let color = Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 0.90,
|
||||
};
|
||||
let color = theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.90);
|
||||
let stroke = canvas::Stroke {
|
||||
width: 1.0,
|
||||
style: canvas::Style::Solid(color),
|
||||
|
|
@ -1010,8 +956,9 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
// the hovered object sits on a locked layer (issue: locked
|
||||
// objects are visible + snappable but not selectable/editable).
|
||||
if self.hover_locked {
|
||||
let amber = Color { r: 0.96, g: 0.76, b: 0.26, a: 0.98 };
|
||||
let dark = Color { r: 0.12, g: 0.10, b: 0.04, a: 1.0 };
|
||||
let warning = theme.extended_palette().warning.base;
|
||||
let amber = warning.color.scale_alpha(0.98);
|
||||
let dark = warning.text;
|
||||
let bx = cp.x + sq + 7.0;
|
||||
let by = cp.y - sq - 13.0;
|
||||
// Lock body (filled).
|
||||
|
|
@ -1058,12 +1005,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
}
|
||||
|
||||
// ── Object Snap Tracking ─────────────────────────────────────────────
|
||||
let track_color = Color {
|
||||
r: 0.15,
|
||||
g: 0.85,
|
||||
b: 0.95,
|
||||
a: 0.7,
|
||||
};
|
||||
let track_color = theme.extended_palette().primary.base.color.scale_alpha(0.7);
|
||||
// The alignment line the cursor is currently locked to — drawn at its
|
||||
// real angle from the acquired point through the lock and a little
|
||||
// beyond, dashed so it reads as a construction guide. This covers the
|
||||
|
|
@ -2142,10 +2084,12 @@ struct DynInputCanvas {
|
|||
}
|
||||
|
||||
impl DynInputCanvas {
|
||||
fn dotted() -> canvas::Stroke<'static> {
|
||||
fn dotted(theme: &Theme) -> canvas::Stroke<'static> {
|
||||
canvas::Stroke {
|
||||
width: 1.0,
|
||||
style: canvas::Style::Solid(Color { r: 0.55, g: 0.55, b: 0.58, a: 0.9 }),
|
||||
style: canvas::Style::Solid(
|
||||
theme.extended_palette().background.neutral.color.scale_alpha(0.9)
|
||||
),
|
||||
line_dash: canvas::LineDash { segments: &[2.0, 3.0], offset: 0 },
|
||||
..Default::default()
|
||||
}
|
||||
|
|
@ -2160,13 +2104,19 @@ impl DynInputCanvas {
|
|||
}
|
||||
|
||||
/// Draw a value box centred at `center`, clamped inside `bounds`.
|
||||
fn draw_box(frame: &mut canvas::Frame, b: &DynBox, center: Point, bounds: iced::Rectangle) {
|
||||
fn draw_box(
|
||||
frame: &mut canvas::Frame,
|
||||
b: &DynBox,
|
||||
center: Point,
|
||||
bounds: iced::Rectangle,
|
||||
theme: &Theme,
|
||||
) {
|
||||
let content = Self::box_content(b);
|
||||
let w = (content.len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0;
|
||||
let x = (center.x - w * 0.5).clamp(0.0, (bounds.width - w).max(0.0));
|
||||
let y = (center.y - DYN_BOX_H * 0.5).clamp(0.0, (bounds.height - DYN_BOX_H).max(0.0));
|
||||
let rect = canvas::Path::rectangle(Point { x, y }, Size { width: w, height: DYN_BOX_H });
|
||||
let (fill, border) = Self::box_colors(b);
|
||||
let (fill, border, text) = Self::box_colors(b, theme);
|
||||
frame.fill(&rect, fill);
|
||||
frame.stroke(
|
||||
&rect,
|
||||
|
|
@ -2177,7 +2127,7 @@ impl DynInputCanvas {
|
|||
frame.fill_text(canvas::Text {
|
||||
content,
|
||||
position: Point { x: x + DYN_PAD, y: y + DYN_PAD },
|
||||
color: Color { r: 0.92, g: 0.92, b: 0.92, a: 1.0 },
|
||||
color: text,
|
||||
size: iced::Pixels(DYN_FONT),
|
||||
// Force Advanced shaping: the default `Auto` uses Basic shaping for
|
||||
// ASCII-only strings, which the web (wgpu/webgl) backend fails to
|
||||
|
|
@ -2188,43 +2138,48 @@ impl DynInputCanvas {
|
|||
});
|
||||
}
|
||||
|
||||
fn box_colors(b: &DynBox) -> (Color, Color) {
|
||||
fn box_colors(b: &DynBox, theme: &Theme) -> (Color, Color, Color) {
|
||||
let palette = theme.extended_palette();
|
||||
if b.active {
|
||||
(
|
||||
Color { r: 0.12, g: 0.18, b: 0.30, a: 0.95 },
|
||||
Color { r: 0.45, g: 0.70, b: 1.0, a: 1.0 },
|
||||
palette.primary.weak.color,
|
||||
palette.primary.base.color,
|
||||
palette.primary.weak.text,
|
||||
)
|
||||
} else if b.locked {
|
||||
(
|
||||
Color { r: 0.05, g: 0.05, b: 0.12, a: 0.9 },
|
||||
Color { r: 0.95, g: 0.75, b: 0.30, a: 0.9 },
|
||||
palette.warning.weak.color,
|
||||
palette.warning.base.color,
|
||||
palette.warning.weak.text,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Color { r: 0.05, g: 0.05, b: 0.12, a: 0.9 },
|
||||
Color { r: 0.35, g: 0.55, b: 0.90, a: 0.9 },
|
||||
palette.background.weak.color,
|
||||
palette.background.neutral.color,
|
||||
palette.background.weak.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prompt pill at `pos`.
|
||||
fn draw_prompt(&self, frame: &mut canvas::Frame, pos: Point) {
|
||||
fn draw_prompt(&self, frame: &mut canvas::Frame, pos: Point, theme: &Theme) {
|
||||
if self.prompt.is_empty() {
|
||||
return;
|
||||
}
|
||||
let palette = theme.extended_palette();
|
||||
let pw = (self.prompt.len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0;
|
||||
let rect = canvas::Path::rectangle(pos, Size { width: pw, height: DYN_BOX_H });
|
||||
frame.fill(&rect, Color { r: 0.10, g: 0.10, b: 0.12, a: 1.0 });
|
||||
frame.fill(&rect, palette.background.strong.color);
|
||||
frame.stroke(
|
||||
&rect,
|
||||
canvas::Stroke::default()
|
||||
.with_color(Color { r: 0.35, g: 0.55, b: 0.90, a: 0.9 })
|
||||
.with_color(palette.primary.base.color.scale_alpha(0.9))
|
||||
.with_width(1.0),
|
||||
);
|
||||
frame.fill_text(canvas::Text {
|
||||
content: self.prompt.clone(),
|
||||
position: Point { x: pos.x + DYN_PAD, y: pos.y + DYN_PAD },
|
||||
color: Color { r: 0.70, g: 0.85, b: 0.70, a: 1.0 },
|
||||
color: palette.background.strong.text,
|
||||
size: iced::Pixels(DYN_FONT),
|
||||
shaping: iced::advanced::text::Shaping::Advanced,
|
||||
..Default::default()
|
||||
|
|
@ -2233,7 +2188,13 @@ impl DynInputCanvas {
|
|||
|
||||
/// Guided layout: draw the guide geometry anchored at `base`, then place
|
||||
/// each box according to its role.
|
||||
fn draw_guided(&self, frame: &mut canvas::Frame, bounds: iced::Rectangle, base: Point) {
|
||||
fn draw_guided(
|
||||
&self,
|
||||
frame: &mut canvas::Frame,
|
||||
bounds: iced::Rectangle,
|
||||
base: Point,
|
||||
theme: &Theme,
|
||||
) {
|
||||
let cursor_raw = self.cursor_screen;
|
||||
let (vx, vy) = (cursor_raw.x - base.x, cursor_raw.y - base.y);
|
||||
let raw_len = (vx * vx + vy * vy).sqrt().max(1.0);
|
||||
|
|
@ -2308,7 +2269,7 @@ impl DynInputCanvas {
|
|||
y: base.y + a_ref.sin() * len,
|
||||
});
|
||||
});
|
||||
frame.stroke(&href, Self::dotted());
|
||||
frame.stroke(&href, Self::dotted(theme));
|
||||
let arc = canvas::Path::new(|p| {
|
||||
let steps = 48;
|
||||
for k in 0..=steps {
|
||||
|
|
@ -2324,14 +2285,14 @@ impl DynInputCanvas {
|
|||
}
|
||||
}
|
||||
});
|
||||
frame.stroke(&arc, Self::dotted());
|
||||
frame.stroke(&arc, Self::dotted(theme));
|
||||
}
|
||||
DynGuide::Radius => {
|
||||
let line = canvas::Path::new(|p| {
|
||||
p.move_to(base);
|
||||
p.line_to(cursor);
|
||||
});
|
||||
frame.stroke(&line, Self::dotted());
|
||||
frame.stroke(&line, Self::dotted(theme));
|
||||
}
|
||||
DynGuide::Perp => {
|
||||
if let Some((end, _, _)) = perp_info {
|
||||
|
|
@ -2340,7 +2301,7 @@ impl DynInputCanvas {
|
|||
p.move_to(base);
|
||||
p.line_to(end);
|
||||
});
|
||||
frame.stroke(&line, Self::dotted());
|
||||
frame.stroke(&line, Self::dotted(theme));
|
||||
}
|
||||
}
|
||||
DynGuide::PerpDim => {
|
||||
|
|
@ -2351,14 +2312,14 @@ impl DynInputCanvas {
|
|||
p.move_to(ob);
|
||||
p.line_to(oe);
|
||||
});
|
||||
frame.stroke(&dim, Self::dotted());
|
||||
frame.stroke(&dim, Self::dotted(theme));
|
||||
let ext = canvas::Path::new(|p| {
|
||||
p.move_to(base);
|
||||
p.line_to(ob);
|
||||
p.move_to(end);
|
||||
p.line_to(oe);
|
||||
});
|
||||
frame.stroke(&ext, Self::dotted());
|
||||
frame.stroke(&ext, Self::dotted(theme));
|
||||
}
|
||||
}
|
||||
DynGuide::AxisDelta | DynGuide::RectSides => {
|
||||
|
|
@ -2368,7 +2329,7 @@ impl DynInputCanvas {
|
|||
p.line_to(corner);
|
||||
p.line_to(cursor);
|
||||
});
|
||||
frame.stroke(&legs, Self::dotted());
|
||||
frame.stroke(&legs, Self::dotted(theme));
|
||||
if self.guide == DynGuide::RectSides {
|
||||
// Close the rectangle so both side pairs read as a box.
|
||||
let rest = canvas::Path::new(|p| {
|
||||
|
|
@ -2376,7 +2337,7 @@ impl DynInputCanvas {
|
|||
p.line_to(Point { x: base.x, y: cursor.y });
|
||||
p.line_to(cursor);
|
||||
});
|
||||
frame.stroke(&rest, Self::dotted());
|
||||
frame.stroke(&rest, Self::dotted(theme));
|
||||
}
|
||||
}
|
||||
DynGuide::None => {}
|
||||
|
|
@ -2423,12 +2384,12 @@ impl DynInputCanvas {
|
|||
y: base.y + dy * len * 0.5 + ny * 16.0,
|
||||
},
|
||||
};
|
||||
Self::draw_box(frame, b, center, bounds);
|
||||
Self::draw_box(frame, b, center, bounds, theme);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallback row layout near the cursor (no anchor / `None` guide).
|
||||
fn draw_row(&self, frame: &mut canvas::Frame, bounds: iced::Rectangle) {
|
||||
fn draw_row(&self, frame: &mut canvas::Frame, bounds: iced::Rectangle, theme: &Theme) {
|
||||
let texts: Vec<String> = self
|
||||
.boxes
|
||||
.iter()
|
||||
|
|
@ -2466,7 +2427,7 @@ impl DynInputCanvas {
|
|||
py = (by - pad - DYN_BOX_H).max(0.0);
|
||||
}
|
||||
if has_prompt {
|
||||
self.draw_prompt(frame, Point { x: bx, y: py });
|
||||
self.draw_prompt(frame, Point { x: bx, y: py }, theme);
|
||||
}
|
||||
|
||||
let mut x = bx;
|
||||
|
|
@ -2474,7 +2435,7 @@ impl DynInputCanvas {
|
|||
let w = widths[i];
|
||||
let rect =
|
||||
canvas::Path::rectangle(Point { x, y: by }, Size { width: w, height: DYN_BOX_H });
|
||||
let (fill, border) = Self::box_colors(b);
|
||||
let (fill, border, text) = Self::box_colors(b, theme);
|
||||
frame.fill(&rect, fill);
|
||||
frame.stroke(
|
||||
&rect,
|
||||
|
|
@ -2485,7 +2446,7 @@ impl DynInputCanvas {
|
|||
frame.fill_text(canvas::Text {
|
||||
content: texts[i].clone(),
|
||||
position: Point { x: x + DYN_PAD, y: by + DYN_PAD },
|
||||
color: Color { r: 0.92, g: 0.92, b: 0.92, a: 1.0 },
|
||||
color: text,
|
||||
size: iced::Pixels(DYN_FONT),
|
||||
shaping: iced::advanced::text::Shaping::Advanced,
|
||||
..Default::default()
|
||||
|
|
@ -2511,7 +2472,7 @@ impl canvas::Program<Message> for DynInputCanvas {
|
|||
&self,
|
||||
_state: &(),
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: iced::Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
|
|
@ -2529,15 +2490,15 @@ impl canvas::Program<Message> for DynInputCanvas {
|
|||
if py + DYN_BOX_H > bounds.height {
|
||||
py = (self.cursor_screen.y - DYN_BOX_H - 4.0).max(0.0);
|
||||
}
|
||||
self.draw_prompt(&mut frame, Point { x: px, y: py });
|
||||
self.draw_prompt(&mut frame, Point { x: px, y: py }, theme);
|
||||
}
|
||||
return vec![frame.into_geometry()];
|
||||
}
|
||||
|
||||
// Guided layouts need the anchor; without it fall back to a cursor row.
|
||||
match (self.guide, self.base_screen) {
|
||||
(DynGuide::None, _) | (_, None) => self.draw_row(&mut frame, bounds),
|
||||
(_, Some(base)) => self.draw_guided(&mut frame, bounds, base),
|
||||
(DynGuide::None, _) | (_, None) => self.draw_row(&mut frame, bounds, theme),
|
||||
(_, Some(base)) => self.draw_guided(&mut frame, bounds, base, theme),
|
||||
}
|
||||
vec![frame.into_geometry()]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//! adds that object to the current selection. Clicking outside dismisses it.
|
||||
|
||||
use iced::widget::{button, column, container, mouse_area, opaque, text};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Element, Fill, Length};
|
||||
|
||||
use crate::app::Message;
|
||||
|
||||
|
|
@ -19,15 +19,7 @@ pub fn cycle_popup_overlay(
|
|||
.collect();
|
||||
|
||||
let panel = container(column(rows))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.width(Length::Fixed(150.0));
|
||||
|
||||
let positioned = crate::ui::pin_at(anchor, opaque(panel));
|
||||
|
|
@ -36,16 +28,10 @@ pub fn cycle_popup_overlay(
|
|||
}
|
||||
|
||||
fn item_row(handle: acadrust::Handle, label: String) -> Element<'static, Message> {
|
||||
let content = text(label).size(11).color(LABEL).align_y(iced::Center);
|
||||
let content = text(label).size(11).align_y(iced::Center);
|
||||
let btn = button(content)
|
||||
.on_press(Message::CycleSelect(handle))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 10]);
|
||||
// Highlight the underlying object while the cursor is over this row.
|
||||
|
|
@ -54,30 +40,3 @@ fn item_row(handle: acadrust::Handle, label: String) -> Element<'static, Message
|
|||
.on_exit(Message::CycleHoverExit(handle))
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const PANEL_BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const PANEL_BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.45,
|
||||
b: 0.62,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Isolate / Hide / End Isolation status menu.
|
||||
|
||||
use iced::widget::{button, row, text};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Element, Fill};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::statusbar::status_menu::Entry;
|
||||
|
|
@ -41,19 +41,11 @@ fn action_entry(label: &'static str, enabled: bool, msg: Message) -> Entry<'stat
|
|||
}
|
||||
|
||||
fn action_row(label: &'static str, enabled: bool, msg: Message) -> Element<'static, Message> {
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
.color(if enabled { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(label).size(11);
|
||||
let content = row![lbl].align_y(iced::Center);
|
||||
|
||||
let mut btn = button(content)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (enabled, status) {
|
||||
(true, button::Status::Hovered) => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 12]);
|
||||
if enabled {
|
||||
|
|
@ -61,24 +53,3 @@ fn action_row(label: &'static str, enabled: bool, msg: Message) -> Element<'stat
|
|||
}
|
||||
btn.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.5,
|
||||
g: 0.5,
|
||||
b: 0.5,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Polar-tracking angle status menu.
|
||||
|
||||
use iced::widget::{button, container, row, text, text_input};
|
||||
use iced::{Background, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Element, Fill, Length};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::statusbar::status_menu::Entry;
|
||||
|
|
@ -41,7 +41,7 @@ pub fn menu_entries<'a>(
|
|||
let custom_row = container(
|
||||
row![
|
||||
custom_field,
|
||||
text("°").size(11).color(LABEL_OFF),
|
||||
text("°").size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -52,51 +52,16 @@ pub fn menu_entries<'a>(
|
|||
}
|
||||
|
||||
fn angle_row<'a>(deg: f32, active: bool) -> Element<'a, Message> {
|
||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
let check = crate::ui::icons::themed_check_cell(active);
|
||||
|
||||
let lbl = text(angle_label(deg))
|
||||
.size(11)
|
||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(angle_label(deg)).size(11);
|
||||
|
||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||
|
||||
button(content)
|
||||
.on_press(Message::SetPolarAngle(deg))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const CHECK_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Annotation / viewport scale status menu.
|
||||
|
||||
use iced::widget::{button, row, text};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Element, Fill};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::statusbar::status_menu::Entry;
|
||||
|
|
@ -44,66 +44,25 @@ pub fn menu_entries(
|
|||
}
|
||||
|
||||
fn scale_row(label: String, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
let check = crate::ui::icons::themed_check_cell(active);
|
||||
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(label).size(11);
|
||||
|
||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||
|
||||
button(content)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn manage_row() -> Element<'static, Message> {
|
||||
button(text("Manage...").size(11).color(CHECK_COLOR))
|
||||
button(text("Manage...").size(11))
|
||||
.on_press(Message::ScaleManagerOpen)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::primary)
|
||||
.width(Fill)
|
||||
.padding([5, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const CHECK_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
use rustc_hash::FxHashSet as HashSet;
|
||||
|
||||
use iced::widget::{button, container, row, text};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::statusbar::status_menu::Entry;
|
||||
|
|
@ -37,8 +37,10 @@ pub fn menu_entries(
|
|||
.padding([4u16, 8]);
|
||||
|
||||
let divider = container(iced::widget::Space::new().height(1))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(DIVIDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -62,99 +64,41 @@ pub fn menu_entries(
|
|||
}
|
||||
|
||||
fn type_row(name: String, included: bool) -> Element<'static, Message> {
|
||||
let check = crate::ui::icons::check_cell(included, CHECK_COLOR);
|
||||
let check = crate::ui::icons::themed_check_cell(included);
|
||||
|
||||
let lbl = text(name.clone())
|
||||
.size(11)
|
||||
.color(if included { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(name.clone()).size(11);
|
||||
|
||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||
|
||||
button(content)
|
||||
.on_press(Message::ToggleSelectionFilterType(name))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn empty_row() -> Element<'static, Message> {
|
||||
container(text("No objects").size(11).color(LABEL_OFF))
|
||||
container(
|
||||
text("No objects").size(11).style(|theme: &Theme| text::Style {
|
||||
color: Some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.42),
|
||||
),
|
||||
}),
|
||||
)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
||||
let color = if enabled {
|
||||
Color { r: 0.70, g: 0.70, b: 0.70, a: 1.0 }
|
||||
} else {
|
||||
Color { r: 0.38, g: 0.38, b: 0.38, a: 1.0 }
|
||||
};
|
||||
let b = button(text(label).size(10).color(color));
|
||||
let b = button(text(label).size(10));
|
||||
let b = if enabled { b.on_press(msg) } else { b };
|
||||
b.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => BTN_BG,
|
||||
})),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
b.style(button::secondary)
|
||||
.padding([3, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const PANEL_BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIVIDER: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_BG: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const CHECK_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! OpenCADStudio-style OSNAP status menu.
|
||||
|
||||
use iced::widget::{button, container, row, text};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Background, Element, Fill, Length, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::snap::{SnapType, Snapper, ALL_SNAP_MODES};
|
||||
|
|
@ -20,8 +20,10 @@ pub fn menu_entries<'a>(snapper: &'a Snapper) -> Vec<Entry<'a>> {
|
|||
|
||||
// Divider
|
||||
let divider = container(iced::widget::Space::new().height(1))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(DIVIDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -41,21 +43,18 @@ pub fn menu_entries<'a>(snapper: &'a Snapper) -> Vec<Entry<'a>> {
|
|||
// ── Individual snap row ───────────────────────────────────────────────────
|
||||
|
||||
fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a, Message> {
|
||||
let checkmark = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
let checkmark = crate::ui::icons::themed_check_cell(active);
|
||||
|
||||
// SVG marker (not a Unicode glyph) so the symbols render on the web build,
|
||||
// whose bundled font lacks them and showed tofu boxes. (#138)
|
||||
let icon_el = container(crate::ui::icons::tinted::<Message>(
|
||||
let icon_el = container(crate::ui::icons::themed_success::<Message>(
|
||||
crate::ui::icons::osnap(snap_type),
|
||||
13.0,
|
||||
ICON_COLOR,
|
||||
))
|
||||
.width(Length::Fixed(16.0))
|
||||
.align_x(iced::Center);
|
||||
|
||||
let label_el = text(label)
|
||||
.size(11)
|
||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
||||
let label_el = text(label).size(11);
|
||||
|
||||
let content = row![checkmark, icon_el, label_el]
|
||||
.spacing(4)
|
||||
|
|
@ -63,98 +62,16 @@ fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a
|
|||
|
||||
button(content)
|
||||
.on_press(Message::ToggleSnap(snap_type))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([3, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
||||
let b = button(text(label).size(10).color(if enabled {
|
||||
Color {
|
||||
r: 0.70,
|
||||
g: 0.70,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.38,
|
||||
g: 0.38,
|
||||
b: 0.38,
|
||||
a: 1.0,
|
||||
}
|
||||
}));
|
||||
let b = button(text(label).size(10));
|
||||
let b = if enabled { b.on_press(msg) } else { b };
|
||||
b.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => BTN_BG,
|
||||
})),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
b.style(button::secondary)
|
||||
.padding([3, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const PANEL_BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIVIDER: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.24,
|
||||
g: 0.24,
|
||||
b: 0.24,
|
||||
a: 1.0,
|
||||
};
|
||||
const BTN_BG: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const CHECK_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const ICON_COLOR: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.75,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
}; // green icon
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.52,
|
||||
g: 0.52,
|
||||
b: 0.52,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Drawing-units status menu.
|
||||
|
||||
use iced::widget::{button, row, text};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Element, Fill};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::statusbar::status_menu::Entry;
|
||||
|
|
@ -49,51 +49,16 @@ pub fn menu_entries(current: i16) -> Vec<Entry<'static>> {
|
|||
}
|
||||
|
||||
fn unit_row(label: &'static str, active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = crate::ui::icons::check_cell(active, CHECK_COLOR);
|
||||
let check = crate::ui::icons::themed_check_cell(active);
|
||||
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(label).size(11);
|
||||
|
||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||
|
||||
button(content)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const CHECK_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -208,9 +208,11 @@ impl PropertiesPanel {
|
|||
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
// ── Header ──────────────────────────────────────────────────────────
|
||||
let header = container(text("Properties").size(12).color(Color::WHITE))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(HEADER_BG)),
|
||||
let header = container(text("Properties").size(12))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
|
|
@ -218,7 +220,10 @@ impl PropertiesPanel {
|
|||
|
||||
// ── Title bar (entity type / "No Selection") ─────────────────────
|
||||
let title_content: Element<'_, Message> = if self.selection_groups.is_empty() {
|
||||
text(crate::ui::text_util::elide(&self.title, 34)).size(FONT_SZ).color(SECTION_LABEL).into()
|
||||
text(crate::ui::text_util::elide(&self.title, 34))
|
||||
.size(FONT_SZ)
|
||||
.style(muted_text_style)
|
||||
.into()
|
||||
} else {
|
||||
combo_box(
|
||||
&self.selection_group_combo,
|
||||
|
|
@ -235,14 +240,17 @@ impl PropertiesPanel {
|
|||
};
|
||||
|
||||
let title_bar = container(title_content)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(SECTION_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weakest.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.padding([4, 10]);
|
||||
|
|
@ -252,7 +260,7 @@ impl PropertiesPanel {
|
|||
container(
|
||||
text("Select an object to view properties")
|
||||
.size(10)
|
||||
.color(HINT_COLOR),
|
||||
.style(hint_text_style),
|
||||
)
|
||||
.padding([10, 10])
|
||||
.into()
|
||||
|
|
@ -265,14 +273,17 @@ impl PropertiesPanel {
|
|||
};
|
||||
|
||||
container(column![header, title_bar, content])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(250)
|
||||
.height(Length::Fill)
|
||||
|
|
@ -286,15 +297,22 @@ impl PropertiesPanel {
|
|||
if self.sections.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let title = container(text(crate::ui::text_util::elide(&self.title, 34)).size(FONT_SZ).color(SECTION_LABEL))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(SECTION_BG)),
|
||||
let title = container(
|
||||
text(crate::ui::text_util::elide(&self.title, 34))
|
||||
.size(FONT_SZ)
|
||||
.style(muted_text_style),
|
||||
)
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weakest.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.padding([4, 10]);
|
||||
|
|
@ -306,14 +324,17 @@ impl PropertiesPanel {
|
|||
|
||||
Some(
|
||||
container(col)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(230)
|
||||
.into(),
|
||||
|
|
@ -324,15 +345,18 @@ impl PropertiesPanel {
|
|||
|
||||
fn render_section<'a>(&'a self, section: &'a PropSection) -> Element<'a, Message> {
|
||||
// Section header
|
||||
let hdr = container(text(§ion.title).size(10).color(Color::WHITE))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(SECTION_HDR_BG)),
|
||||
let hdr = container(text(§ion.title).size(10))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.padding([3, 8]);
|
||||
|
|
@ -508,31 +532,25 @@ impl PropertiesPanel {
|
|||
fn render_color_varies_row<'a>(&'a self, label: &'a str) -> Element<'a, Message> {
|
||||
let color_btn = button(
|
||||
row![
|
||||
container(text("?").size(10).color(VALUE_COLOR))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
})),
|
||||
container(text("?").size(10))
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.5
|
||||
},
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into()
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(SWATCH_SZ)
|
||||
.height(SWATCH_SZ)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
text(VARIES_LABEL).size(FONT_SZ).color(VALUE_COLOR),
|
||||
text(VARIES_LABEL).size(FONT_SZ),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -724,7 +742,7 @@ impl PropertiesPanel {
|
|||
.on_input(move |v| Message::PropGeomInput { field, value: v })
|
||||
.on_submit(Message::PropGeomCommit(field))
|
||||
.size(FONT_SZ)
|
||||
.style(|_: &Theme, status| text_input::Style {
|
||||
.style(|theme: &Theme, status| text_input::Style {
|
||||
// The wrapping container draws the border; keep the input flat
|
||||
// so field + caret read as one control.
|
||||
border: Border {
|
||||
|
|
@ -732,33 +750,32 @@ impl PropertiesPanel {
|
|||
width: 0.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..text_input_style(&Theme::Dark, status)
|
||||
..text_input_style(theme, status)
|
||||
})
|
||||
.padding([3, 6])
|
||||
.width(Length::Fill);
|
||||
let caret = button(
|
||||
container(crate::ui::icons::arrow_toggle(
|
||||
self.edit_choice_open,
|
||||
FONT_SZ,
|
||||
VALUE_COLOR,
|
||||
))
|
||||
container(if self.edit_choice_open {
|
||||
crate::ui::icons::themed_arrow_up(FONT_SZ)
|
||||
} else {
|
||||
crate::ui::icons::themed_arrow_down(FONT_SZ)
|
||||
})
|
||||
.height(Length::Fill)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::PropEditChoiceToggle)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => VALUE_BG,
|
||||
})),
|
||||
text_color: VALUE_COLOR,
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border::default(),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
|
|
@ -768,14 +785,17 @@ impl PropertiesPanel {
|
|||
})
|
||||
.height(Length::Fixed(ROW_H - 6.0));
|
||||
let head = container(row![input, caret].align_y(iced::Center))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(VALUE_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill);
|
||||
|
||||
|
|
@ -794,39 +814,15 @@ impl PropertiesPanel {
|
|||
}
|
||||
let value = opt.clone();
|
||||
list = list.push(
|
||||
button(text(opt.as_str()).size(FONT_SZ).color(VALUE_COLOR))
|
||||
button(text(opt.as_str()).size(FONT_SZ))
|
||||
.on_press(Message::PropGeomChoiceChanged { field, value })
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
}),
|
||||
),
|
||||
text_color: VALUE_COLOR,
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([2, 6])
|
||||
.width(Length::Fill),
|
||||
);
|
||||
}
|
||||
let popup = container(scrollable(list).height(Length::Shrink))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.17,
|
||||
g: 0.17,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
})),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.padding(2)
|
||||
.width(200)
|
||||
.max_height(220.0);
|
||||
|
|
@ -940,13 +936,13 @@ pub fn color_picker_dropdown<'a>(
|
|||
r.push(
|
||||
button(text("").width(18).height(18))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
.style(move |theme: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: if matches!(status, button::Status::Hovered) {
|
||||
Color::WHITE
|
||||
theme.extended_palette().primary.base.color
|
||||
} else {
|
||||
Color::BLACK
|
||||
theme.extended_palette().background.neutral.color
|
||||
},
|
||||
width: if matches!(status, button::Status::Hovered) {
|
||||
1.5
|
||||
|
|
@ -965,20 +961,20 @@ pub fn color_picker_dropdown<'a>(
|
|||
// "More Colors…" toggle button
|
||||
let more_btn = button(
|
||||
row![
|
||||
crate::ui::icons::arrow_toggle(palette_open, 9.0, HINT_COLOR),
|
||||
if palette_open {
|
||||
crate::ui::icons::themed_arrow_up(9.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_arrow_down(9.0)
|
||||
},
|
||||
text(if palette_open { "Less" } else { "More Colors…" })
|
||||
.size(10)
|
||||
.color(HINT_COLOR),
|
||||
.style(hint_text_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(palette_toggle_msg)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(PICKER_BG)),
|
||||
text_color: HINT_COLOR,
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([2, 6])
|
||||
.width(Length::Fill);
|
||||
|
||||
|
|
@ -989,14 +985,17 @@ pub fn color_picker_dropdown<'a>(
|
|||
};
|
||||
|
||||
let mut col = column![container(inner)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PICKER_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([6, 8])
|
||||
.width(Length::Fill)]
|
||||
|
|
@ -1019,18 +1018,13 @@ pub fn color_picker_dropdown<'a>(
|
|||
r = r.push(
|
||||
button(text("").width(12).height(12))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
.style(move |theme: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: if matches!(status, button::Status::Hovered) {
|
||||
Color::WHITE
|
||||
theme.extended_palette().primary.base.color
|
||||
} else {
|
||||
Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.4,
|
||||
}
|
||||
theme.extended_palette().background.neutral.color
|
||||
},
|
||||
width: if matches!(status, button::Status::Hovered) {
|
||||
1.5
|
||||
|
|
@ -1049,14 +1043,17 @@ pub fn color_picker_dropdown<'a>(
|
|||
}
|
||||
col = col.push(
|
||||
container(scrollable(rows).height(160))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PICKER_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 6])
|
||||
.width(Length::Fill),
|
||||
|
|
@ -1071,22 +1068,23 @@ pub fn color_picker_dropdown<'a>(
|
|||
/// A boolean toggle button row (for "Invisible" etc.).
|
||||
fn render_stepper_row<'a>(label: &'a str, display: &'a str) -> Element<'a, Message> {
|
||||
let arrow = |glyph: &'static str, delta: i8| {
|
||||
button(text(glyph).size(FONT_SZ).color(VALUE_COLOR))
|
||||
button(text(glyph).size(FONT_SZ))
|
||||
.on_press(Message::PropVertexStep(delta))
|
||||
.padding([0, 6])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
|
||||
_ => VALUE_BG,
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: VALUE_COLOR,
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
|
|
@ -1095,7 +1093,6 @@ fn render_stepper_row<'a>(label: &'a str, display: &'a str) -> Element<'a, Messa
|
|||
arrow("◀", -1),
|
||||
text(display)
|
||||
.size(FONT_SZ)
|
||||
.color(VALUE_COLOR)
|
||||
.width(Length::Fill)
|
||||
.align_x(iced::Center),
|
||||
arrow("▶", 1),
|
||||
|
|
@ -1111,22 +1108,29 @@ fn render_bool_row<'a>(label: &'a str, field: &'static str, value: bool) -> Elem
|
|||
button(
|
||||
text(btn_label)
|
||||
.size(FONT_SZ)
|
||||
.color(if value { WARN_COLOR } else { VALUE_COLOR }),
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: value.then_some(theme.extended_palette().warning.base.color),
|
||||
}),
|
||||
)
|
||||
.on_press(Message::PropBoolToggle(field))
|
||||
.style(move |_: &Theme, status| {
|
||||
let bg = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
|
||||
_ => VALUE_BG,
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: if value { WARN_COLOR } else { VALUE_COLOR },
|
||||
text_color: if value {
|
||||
palette.warning.base.color
|
||||
} else {
|
||||
pair.text
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
|
|
@ -1211,10 +1215,14 @@ fn render_group_row(
|
|||
let label_btn = button(
|
||||
container(
|
||||
row![
|
||||
crate::ui::icons::arrow_toggle(expanded, FONT_SZ, LABEL_COLOR),
|
||||
if expanded {
|
||||
crate::ui::icons::themed_arrow_down(FONT_SZ)
|
||||
} else {
|
||||
crate::ui::icons::themed_arrow_right(FONT_SZ)
|
||||
},
|
||||
text(crate::ui::text_util::elide(base, 16))
|
||||
.size(FONT_SZ)
|
||||
.color(LABEL_COLOR),
|
||||
.style(muted_text_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -1223,20 +1231,7 @@ fn render_group_row(
|
|||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::PropGroupToggle(key))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.24,
|
||||
g: 0.24,
|
||||
b: 0.24,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => LABEL_BG,
|
||||
})),
|
||||
text_color: LABEL_COLOR,
|
||||
border: Border::default(),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
|
|
@ -1246,8 +1241,10 @@ fn render_group_row(
|
|||
.width(Length::Fill)
|
||||
.height(Length::Fixed(ROW_H));
|
||||
let label_col = container(label_btn)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LABEL_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::FillPortion(5))
|
||||
|
|
@ -1262,8 +1259,10 @@ fn render_group_row(
|
|||
.padding([3, 6])
|
||||
.width(Length::Fill);
|
||||
let value_col = container(value_field)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(VALUE_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::FillPortion(6))
|
||||
|
|
@ -1278,9 +1277,9 @@ fn render_group_row(
|
|||
|
||||
container(row![label_col, value_col])
|
||||
.height(Length::Fixed(ROW_H))
|
||||
.style(|_: &Theme| container::Style {
|
||||
.style(|theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: theme.extended_palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
|
|
@ -1305,9 +1304,15 @@ fn render_ro_row<'a>(label: &'a str, value: &'a str) -> Element<'a, Message> {
|
|||
|
||||
/// Build a label | widget property row.
|
||||
fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
|
||||
let label_col = container(text(crate::ui::text_util::elide(label, 18)).size(FONT_SZ).color(LABEL_COLOR))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LABEL_BG)),
|
||||
let label_col = container(
|
||||
text(crate::ui::text_util::elide(label, 18))
|
||||
.size(FONT_SZ)
|
||||
.style(muted_text_style),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::FillPortion(5))
|
||||
|
|
@ -1320,8 +1325,10 @@ fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<
|
|||
right: 6.0,
|
||||
});
|
||||
let value_col = container(widget)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(VALUE_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::FillPortion(6))
|
||||
|
|
@ -1335,9 +1342,9 @@ fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<
|
|||
});
|
||||
container(row![label_col, value_col])
|
||||
.height(Length::Fixed(ROW_H))
|
||||
.style(|_: &Theme| container::Style {
|
||||
.style(|theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: theme.extended_palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
|
|
@ -1348,18 +1355,9 @@ fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<
|
|||
|
||||
/// A plain text button used inside the color picker for ByLayer / ByBlock.
|
||||
fn picker_text_btn(label: &str, msg: Message) -> Element<'_, Message> {
|
||||
button(text(label).size(FONT_SZ).color(VALUE_COLOR))
|
||||
button(text(label).size(FONT_SZ))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(LABEL_BG)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: VALUE_COLOR,
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::secondary)
|
||||
.padding([2, 8])
|
||||
.into()
|
||||
}
|
||||
|
|
@ -1419,49 +1417,41 @@ fn aci_label(idx: u8) -> &'static str {
|
|||
|
||||
// ── Widget style helpers ──────────────────────────────────────────────────
|
||||
|
||||
fn combo_btn_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
|
||||
_ => VALUE_BG,
|
||||
fn combo_btn_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: VALUE_COLOR,
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn text_input_style(_theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
fn text_input_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border_color = match status {
|
||||
text_input::Status::Focused { .. } => Color {
|
||||
r: 0.3,
|
||||
g: 0.6,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => BORDER,
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(VALUE_BG),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: border_color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: Color::TRANSPARENT,
|
||||
placeholder: HINT_COLOR,
|
||||
value: VALUE_COLOR,
|
||||
selection: Color {
|
||||
r: 0.2,
|
||||
g: 0.4,
|
||||
b: 0.8,
|
||||
a: 0.5,
|
||||
},
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1472,109 +1462,30 @@ fn combo_input_style(theme: &Theme, status: text_input::Status) -> text_input::S
|
|||
/// Style for a read-only-but-selectable value field: flat (no input box or
|
||||
/// focus highlight, so it reads as plain text, unlike the bordered editable
|
||||
/// fields) yet with a visible selection colour so Ctrl+C copy is discoverable.
|
||||
fn ro_input_style(_theme: &Theme, _status: text_input::Status) -> text_input::Style {
|
||||
fn ro_input_style(theme: &Theme, _status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
text_input::Style {
|
||||
background: Background::Color(VALUE_BG),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
icon: Color::TRANSPARENT,
|
||||
placeholder: HINT_COLOR,
|
||||
value: VALUE_COLOR,
|
||||
selection: Color {
|
||||
r: 0.2,
|
||||
g: 0.4,
|
||||
b: 0.8,
|
||||
a: 0.5,
|
||||
},
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Colour constants ──────────────────────────────────────────────────────
|
||||
fn muted_text_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.72)),
|
||||
}
|
||||
}
|
||||
|
||||
const PANEL_BG: Color = Color {
|
||||
r: 0.19,
|
||||
g: 0.19,
|
||||
b: 0.19,
|
||||
a: 1.0,
|
||||
};
|
||||
const HEADER_BG: Color = Color {
|
||||
r: 0.24,
|
||||
g: 0.24,
|
||||
b: 0.24,
|
||||
a: 1.0,
|
||||
};
|
||||
const SECTION_BG: Color = Color {
|
||||
r: 0.21,
|
||||
g: 0.21,
|
||||
b: 0.21,
|
||||
a: 1.0,
|
||||
};
|
||||
const SECTION_HDR_BG: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_BG: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const VALUE_BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
const HOVER_BG: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
};
|
||||
const PICKER_BG: Color = Color {
|
||||
r: 0.16,
|
||||
g: 0.16,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_COLOR: Color = Color {
|
||||
r: 0.70,
|
||||
g: 0.70,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const VALUE_COLOR: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const HINT_COLOR: Color = Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.50,
|
||||
a: 1.0,
|
||||
};
|
||||
const SECTION_LABEL: Color = Color {
|
||||
r: 0.75,
|
||||
g: 0.75,
|
||||
b: 0.75,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const WARN_COLOR: Color = Color {
|
||||
r: 1.00,
|
||||
g: 0.60,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
fn hint_text_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.48)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use iced::advanced::layout::{self, Layout};
|
|||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
||||
use iced::{
|
||||
Background, Border, Color, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
|
||||
Background, Border, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
|
||||
Theme, Vector,
|
||||
};
|
||||
|
||||
|
|
@ -113,8 +113,6 @@ pub struct CollapsePanels<'a> {
|
|||
open: Option<String>,
|
||||
/// Fallback row height, used only when there are no panels to measure.
|
||||
row_h: f32,
|
||||
/// Colour of the 1px divider drawn between panels.
|
||||
divider: Color,
|
||||
/// Chosen degradation level per panel; set during layout.
|
||||
levels: RefCell<Vec<u8>>,
|
||||
/// If set, the measured row height is written here each layout (read when
|
||||
|
|
@ -129,13 +127,12 @@ pub struct CollapsePanels<'a> {
|
|||
}
|
||||
|
||||
impl<'a> CollapsePanels<'a> {
|
||||
pub fn new(panels: Vec<Panel<'a>>, open: Option<String>, row_h: f32, divider: Color) -> Self {
|
||||
pub fn new(panels: Vec<Panel<'a>>, open: Option<String>, row_h: f32) -> Self {
|
||||
let n = panels.len();
|
||||
Self {
|
||||
panels,
|
||||
open,
|
||||
row_h,
|
||||
divider,
|
||||
levels: RefCell::new(vec![FULL; n]),
|
||||
height_out: None,
|
||||
tight_out: None,
|
||||
|
|
@ -480,7 +477,7 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
|||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Background::Color(self.divider),
|
||||
Background::Color(theme.extended_palette().background.neutral.color),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use std::sync::Arc;
|
|||
use rustc_hash::FxHashMap as HashMap;
|
||||
|
||||
use acadrust::types::{Color as AcadColor, LineWeight};
|
||||
use iced::widget::{button, column, container, mouse_area, row, scrollable, svg, text};
|
||||
use iced::widget::{button, column, container, mouse_area, row, scrollable, text};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
|
|
@ -419,58 +419,41 @@ impl Ribbon {
|
|||
|
||||
let is_active = i == self.active;
|
||||
let is_contextual = module.id() == "layout";
|
||||
let accent = if is_contextual {
|
||||
ACCENT_GOLD
|
||||
} else {
|
||||
ACCENT_BLUE
|
||||
};
|
||||
let text_inactive = if is_contextual {
|
||||
Color {
|
||||
r: 0.90,
|
||||
g: 0.72,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.75,
|
||||
g: 0.75,
|
||||
b: 0.75,
|
||||
a: 1.0,
|
||||
}
|
||||
};
|
||||
let hover_bg = if is_contextual {
|
||||
Color {
|
||||
r: 0.28,
|
||||
g: 0.24,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
}
|
||||
};
|
||||
let btn = container(
|
||||
button(text(module.title()).size(12))
|
||||
.on_press(Message::RibbonSelectTab(i))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_active, status) {
|
||||
(true, _) => RIBBON_BG,
|
||||
(false, button::Status::Hovered) => hover_bg,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: if is_active {
|
||||
Color::WHITE
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let accent = if is_contextual {
|
||||
palette.warning.base
|
||||
} else {
|
||||
text_inactive
|
||||
palette.primary.base
|
||||
};
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.background.weakest,
|
||||
(false, button::Status::Hovered) => {
|
||||
if is_contextual {
|
||||
palette.warning.weak
|
||||
} else {
|
||||
palette.background.weak
|
||||
}
|
||||
}
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: (is_active
|
||||
|| matches!(status, button::Status::Hovered))
|
||||
.then_some(Background::Color(pair.color)),
|
||||
text_color: if is_active {
|
||||
pair.text
|
||||
} else if is_contextual {
|
||||
accent.color
|
||||
} else {
|
||||
palette.background.base.text.scale_alpha(0.72)
|
||||
},
|
||||
border: Border {
|
||||
color: if is_active {
|
||||
accent
|
||||
accent.color
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
|
|
@ -479,13 +462,18 @@ impl Ribbon {
|
|||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
}
|
||||
})
|
||||
.padding([5, 14]),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
color: if is_active {
|
||||
accent
|
||||
if is_contextual {
|
||||
theme.extended_palette().warning.base.color
|
||||
} else {
|
||||
theme.extended_palette().primary.base.color
|
||||
}
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
|
|
@ -514,9 +502,11 @@ impl Ribbon {
|
|||
// persisted (see `Ribbon::set_collapse_mode`). It hides itself once the
|
||||
// tool row is tight, giving the cramped tab row its space back.
|
||||
let dd_open = self.open_dropdown.as_deref() == Some(COLLAPSE_MODE_ID);
|
||||
let mode_btn = button(crate::ui::icons::arrow_down(10.0, ARROW_COLOR))
|
||||
let mode_btn = button(crate::ui::icons::themed_arrow_down(10.0))
|
||||
.on_press(Message::ToggleRibbonDropdown(COLLAPSE_MODE_ID.to_string()))
|
||||
.style(move |_: &Theme, status| top_hist_btn_style(true, dd_open, status))
|
||||
.style(move |theme: &Theme, status| {
|
||||
top_hist_btn_style(theme, true, dd_open, status)
|
||||
})
|
||||
.height(24)
|
||||
.padding([2, 8]);
|
||||
let mode_dd = PosReport::new(COLLAPSE_MODE_ID, mode_btn);
|
||||
|
|
@ -534,8 +524,10 @@ impl Ribbon {
|
|||
}
|
||||
|
||||
let tab_bar = container(tab_row)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TOPBAR_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
|
|
@ -653,10 +645,12 @@ impl Ribbon {
|
|||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(RIBBON_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
border: Border {
|
||||
color: BORDER_DARK,
|
||||
color: theme.extended_palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
|
|
@ -666,7 +660,7 @@ impl Ribbon {
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
CollapsePanels::new(panels, self.collapsed_open.clone(), TOOL_BAR_H, BORDER_DARK)
|
||||
CollapsePanels::new(panels, self.collapsed_open.clone(), TOOL_BAR_H)
|
||||
.report_height(self.tool_bar_h.clone())
|
||||
.report_tight(self.collapse_tight.clone())
|
||||
.mode(self.collapse_mode)
|
||||
|
|
@ -676,10 +670,12 @@ impl Ribbon {
|
|||
};
|
||||
|
||||
let tool_bar = container(tool_area)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(RIBBON_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
border: Border {
|
||||
color: BORDER_DARK,
|
||||
color: theme.extended_palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
|
|
@ -712,19 +708,13 @@ impl Ribbon {
|
|||
.enumerate()
|
||||
.map(|(idx, label)| {
|
||||
let step = idx + 1;
|
||||
button(text(label.clone()).size(11).color(LABEL_ON))
|
||||
button(text(label.clone()).size(11))
|
||||
.on_press(if is_undo {
|
||||
Message::UndoMany(step)
|
||||
} else {
|
||||
Message::RedoMany(step)
|
||||
})
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([5, 10])
|
||||
.into()
|
||||
|
|
@ -732,15 +722,7 @@ impl Ribbon {
|
|||
.collect();
|
||||
|
||||
let panel = container(column(rows))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(170.0));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, 170.0, win.0);
|
||||
|
|
@ -755,27 +737,16 @@ impl Ribbon {
|
|||
let rows: Vec<Element<Message>> = CollapseMode::ALL
|
||||
.iter()
|
||||
.map(|&m| {
|
||||
let mark: Element<Message> = if m == current {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
};
|
||||
button(
|
||||
row![
|
||||
container(mark).width(Length::Fixed(16.0)),
|
||||
text(m.label()).size(11).color(LABEL_ON),
|
||||
crate::ui::icons::themed_check_cell(m == current),
|
||||
text(m.label()).size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::SetRibbonCollapseMode(m))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([5, 10])
|
||||
.into()
|
||||
|
|
@ -783,15 +754,7 @@ impl Ribbon {
|
|||
.collect();
|
||||
|
||||
let panel = container(column(rows))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(W));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, W, win.0);
|
||||
|
|
@ -852,28 +815,25 @@ impl Ribbon {
|
|||
.iter()
|
||||
.map(|(cmd, label, item_icon)| {
|
||||
let is_current = *cmd == last_cmd;
|
||||
let checkmark: Element<'_, Message> = container(if is_current {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let icon_el: Element<Message> = match *item_icon {
|
||||
IconKind::Glyph(s) => text(s)
|
||||
.size(13)
|
||||
.color(ICON_COLOR)
|
||||
let checkmark: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_current);
|
||||
let icon_el: Element<Message> =
|
||||
container(make_icon(*item_icon, 20.0))
|
||||
.width(Length::Fixed(20.0))
|
||||
.into(),
|
||||
IconKind::Svg(bytes) => {
|
||||
let handle = svg::Handle::from_memory(bytes);
|
||||
svg(handle).width(20).height(20).into()
|
||||
}
|
||||
};
|
||||
.into();
|
||||
let label_el =
|
||||
text(*label)
|
||||
.size(11)
|
||||
.color(if is_current { LABEL_ON } else { LABEL_OFF });
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_current).then_some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
});
|
||||
|
||||
button(
|
||||
row![checkmark, icon_el, label_el]
|
||||
|
|
@ -884,13 +844,7 @@ impl Ribbon {
|
|||
dropdown_id: dd_id,
|
||||
cmd: *cmd,
|
||||
})
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
|
|
@ -898,15 +852,7 @@ impl Ribbon {
|
|||
.collect();
|
||||
|
||||
let panel = container(column(rows))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(190.0));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, 190.0, win.0);
|
||||
|
|
@ -925,13 +871,7 @@ impl Ribbon {
|
|||
let icon_btn = |bytes: &'static [u8], msg: Message| -> Element<'_, Message> {
|
||||
button(crate::ui::icons::raw(bytes, 14.0))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.padding([2, 4])
|
||||
.into()
|
||||
};
|
||||
|
|
@ -952,10 +892,10 @@ impl Ribbon {
|
|||
let name = info.name.clone();
|
||||
|
||||
let swatch = container(text(""))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(lc)),
|
||||
border: Border {
|
||||
color: SWATCH_BORDER,
|
||||
color: theme.extended_palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 1.0.into(),
|
||||
},
|
||||
|
|
@ -976,29 +916,27 @@ impl Ribbon {
|
|||
crate::ui::icons::layer_lock(ll),
|
||||
Message::LayerToggleLock(index),
|
||||
);
|
||||
let checkmark: Element<'_, Message> = container(if is_active {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let checkmark: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_active);
|
||||
let label =
|
||||
text(&info.name)
|
||||
.size(11)
|
||||
.color(if is_active { LABEL_ON } else { LABEL_OFF });
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_active).then_some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
});
|
||||
|
||||
// The swatch + label area selects the layer as active; the
|
||||
// icon buttons above handle their own toggles.
|
||||
let select = button(row![swatch, label].spacing(5).align_y(iced::Center))
|
||||
.on_press(Message::RibbonLayerChanged(name))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([4, 4]);
|
||||
|
||||
|
|
@ -1021,34 +959,7 @@ impl Ribbon {
|
|||
let search = iced::widget::text_input("Search layers…", &self.layer_filter)
|
||||
.on_input(Message::RibbonLayerFilterChanged)
|
||||
.size(11)
|
||||
.padding([4, 6])
|
||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
||||
background: Background::Color(Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
}),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.5,
|
||||
},
|
||||
});
|
||||
.padding([4, 6]);
|
||||
let panel = container(
|
||||
column![
|
||||
container(search).padding([4, 4]),
|
||||
|
|
@ -1056,15 +967,7 @@ impl Ribbon {
|
|||
]
|
||||
.spacing(2),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(220.0));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(LAYER_COMBO_ID, 220.0, win.0);
|
||||
|
|
@ -1113,35 +1016,29 @@ impl Ribbon {
|
|||
};
|
||||
let active = ctx.active_for(style_key).to_string();
|
||||
|
||||
let row_style = |_: &Theme, status: button::Status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut rows: Vec<Element<Message>> = ctx
|
||||
.names_for(style_key)
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let is_sel = name.as_str() == active.as_str();
|
||||
let n = name.clone();
|
||||
let checkmark: Element<Message> = container(if is_sel {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let checkmark: Element<Message> =
|
||||
crate::ui::icons::themed_check_cell(is_sel);
|
||||
button(
|
||||
row![
|
||||
checkmark,
|
||||
text(name.clone()).size(11).color(if is_sel {
|
||||
LABEL_ON
|
||||
} else {
|
||||
LABEL_OFF
|
||||
}),
|
||||
text(name.clone())
|
||||
.size(11)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_sel).then_some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
}),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -1150,7 +1047,7 @@ impl Ribbon {
|
|||
key: style_key,
|
||||
name: n,
|
||||
})
|
||||
.style(row_style)
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
|
|
@ -1159,9 +1056,9 @@ impl Ribbon {
|
|||
|
||||
if let Some(mgr) = manager_cmd {
|
||||
rows.push(
|
||||
button(text("Manage…").size(11).color(LABEL_ON))
|
||||
button(text("Manage…").size(11))
|
||||
.on_press(Message::Command(mgr.to_string()))
|
||||
.style(row_style)
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into(),
|
||||
|
|
@ -1169,15 +1066,7 @@ impl Ribbon {
|
|||
}
|
||||
|
||||
let panel = container(column(rows))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(LARGE_W * 2.3));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, LARGE_W * 2.3, win.0);
|
||||
|
|
@ -1200,15 +1089,7 @@ impl Ribbon {
|
|||
);
|
||||
|
||||
let panel = container(picker)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(200.0));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(PROP_COLOR_ID, 200.0, win.0);
|
||||
|
|
@ -1243,23 +1124,22 @@ impl Ribbon {
|
|||
.into_iter()
|
||||
.map(|lt| {
|
||||
let is_cur = lt.name == *active_lt;
|
||||
let check: Element<'_, Message> = container(if is_cur {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let check: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_cur);
|
||||
let name_col = text(lt.name.clone())
|
||||
.size(11)
|
||||
.color(if is_cur { LABEL_ON } else { LABEL_OFF })
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_cur).then_some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
})
|
||||
.width(Length::Fixed(90.0));
|
||||
let art_col = text(lt.art.clone()).size(9).color(Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
});
|
||||
let art_col = text(lt.art.clone()).size(9).style(muted_text_style);
|
||||
let name = lt.name.clone();
|
||||
button(
|
||||
row![check, name_col, art_col]
|
||||
|
|
@ -1267,13 +1147,7 @@ impl Ribbon {
|
|||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::RibbonLinetypeChanged(name))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([4, 6])
|
||||
.into()
|
||||
|
|
@ -1281,15 +1155,7 @@ impl Ribbon {
|
|||
.collect();
|
||||
|
||||
let list = container(scrollable(column(rows)).height(Length::Fixed(200.0)))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(220.0));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(PROP_LINETYPE_ID, 220.0, win.0);
|
||||
|
|
@ -1308,31 +1174,29 @@ impl Ribbon {
|
|||
.map(|item| {
|
||||
let is_cur = item.0 == active_lw;
|
||||
let label = item.to_string();
|
||||
let check: Element<'_, Message> = container(if is_cur {
|
||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).into()
|
||||
})
|
||||
.width(Length::Fixed(14.0))
|
||||
.into();
|
||||
let check: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_cur);
|
||||
button(
|
||||
row![
|
||||
check,
|
||||
text(label)
|
||||
.size(11)
|
||||
.color(if is_cur { LABEL_ON } else { LABEL_OFF })
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_cur).then_some(
|
||||
theme
|
||||
.extended_palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
})
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::RibbonLineweightChanged(item.0))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_row_style)
|
||||
.width(Fill)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
|
|
@ -1350,15 +1214,7 @@ impl Ribbon {
|
|||
win: (f32, f32),
|
||||
) -> Option<Element<'a, Message>> {
|
||||
let panel = container(column(rows))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(width));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(dd_id, width, win.0);
|
||||
|
|
@ -1439,7 +1295,7 @@ fn render_group<'a>(
|
|||
|
||||
column![
|
||||
tools_el,
|
||||
container(text(group.title).size(9).color(GROUP_LABEL)).padding([1, 4]),
|
||||
container(text(group.title).size(9).style(muted_text_style)).padding([1, 4]),
|
||||
]
|
||||
.align_x(iced::Center)
|
||||
.spacing(0)
|
||||
|
|
@ -1526,8 +1382,8 @@ fn collapse_button<'a>(
|
|||
column![
|
||||
icon,
|
||||
row![
|
||||
text(title.to_string()).size(9).color(GROUP_LABEL),
|
||||
crate::ui::icons::arrow_down(8.0, GROUP_LABEL),
|
||||
text(title.to_string()).size(9).style(muted_text_style),
|
||||
crate::ui::icons::themed_secondary_arrow_down(8.0),
|
||||
]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -1536,22 +1392,7 @@ fn collapse_button<'a>(
|
|||
.spacing(2),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonPanel(title.to_string()))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([3, 5])
|
||||
.into();
|
||||
}
|
||||
|
|
@ -1594,29 +1435,14 @@ fn collapse_button<'a>(
|
|||
|
||||
let opener = button(
|
||||
row![
|
||||
text(title.to_string()).size(9).color(GROUP_LABEL),
|
||||
crate::ui::icons::arrow_down(8.0, GROUP_LABEL),
|
||||
text(title.to_string()).size(9).style(muted_text_style),
|
||||
crate::ui::icons::themed_secondary_arrow_down(8.0),
|
||||
]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonPanel(title.to_string()))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([1, 4]);
|
||||
|
||||
// The large face fills a fixed slot so a collapsed panel is shorter than a full
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use acadrust::types::{Color as AcadColor, LineWeight};
|
|||
// Ribbon tooltips anchor to the right of their button so the cursor — which
|
||||
// rests on the button itself — never covers the tip text. (#143)
|
||||
use iced::widget::tooltip::Position as TipPos;
|
||||
use iced::widget::{button, column, container, row, svg, text, tooltip};
|
||||
use iced::widget::{button, column, container, row, text, tooltip};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
|
|
@ -77,175 +77,6 @@ pub(super) const PROP_COLOR_ID: &str = "PROP_COLOR";
|
|||
pub(super) const PROP_LINETYPE_ID: &str = "PROP_LINETYPE";
|
||||
pub(super) const PROP_LW_ID: &str = "PROP_LW";
|
||||
|
||||
// ── Colours ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Light chrome grey for the quick-access file-command icons on the top strip.
|
||||
pub(super) const QA_ICON_COLOR: Color = Color {
|
||||
r: 0.82,
|
||||
g: 0.83,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const TOPBAR_BG: Color = Color {
|
||||
r: 0.17,
|
||||
g: 0.17,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const RIBBON_BG: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const BORDER_DARK: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const ACCENT_BLUE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.55,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const ACCENT_GOLD: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.65,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const LABEL_COLOR: Color = Color {
|
||||
r: 0.82,
|
||||
g: 0.82,
|
||||
b: 0.82,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const GROUP_LABEL: Color = Color {
|
||||
r: 0.50,
|
||||
g: 0.50,
|
||||
b: 0.50,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const TOOL_HOVER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const TOOL_ACTIVE: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.42,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const ARROW_COLOR: Color = Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const PANEL_BG: Color = Color {
|
||||
r: 0.16,
|
||||
g: 0.16,
|
||||
b: 0.16,
|
||||
a: 0.98,
|
||||
};
|
||||
pub(super) const PANEL_BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const ROW_HOVER: Color = Color {
|
||||
r: 0.24,
|
||||
g: 0.24,
|
||||
b: 0.24,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const CHECK_COLOR: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.75,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const ICON_COLOR: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.75,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const LABEL_OFF: Color = Color {
|
||||
r: 0.72,
|
||||
g: 0.72,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
// ── Combo / dropdown colors ───────────────────────────────────────────────
|
||||
|
||||
pub(super) const COMBO_BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const COMBO_HOVER_BG: Color = Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const COMBO_OPEN_BG: Color = Color {
|
||||
r: 0.14,
|
||||
g: 0.14,
|
||||
b: 0.14,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const COMBO_BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const COMBO_ACTIVE_BORDER: Color = Color {
|
||||
r: 0.45,
|
||||
g: 0.65,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const COMBO_ARROW: Color = Color {
|
||||
r: 0.70,
|
||||
g: 0.70,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(super) const SWATCH_BORDER: Color = Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.5,
|
||||
};
|
||||
pub(super) const TIP_BG: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 0.97,
|
||||
};
|
||||
pub(super) const HIST_INACTIVE_BG: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
// ── Style context (passed from Ribbon to render_large) ────────────────────
|
||||
|
||||
pub(super) struct StyleContext {
|
||||
|
|
@ -294,11 +125,8 @@ pub(super) fn flush_small_col<'a>(
|
|||
|
||||
pub(super) fn make_icon(icon: IconKind, size: f32) -> Element<'static, Message> {
|
||||
match icon {
|
||||
IconKind::Glyph(s) => text(s).size(size * 0.7).color(Color::WHITE).into(),
|
||||
IconKind::Svg(bytes) => {
|
||||
let handle = svg::Handle::from_memory(bytes);
|
||||
svg(handle).width(size).height(size).into()
|
||||
}
|
||||
IconKind::Glyph(s) => text(s).size(size * 0.7).into(),
|
||||
IconKind::Svg(bytes) => icons::themed(bytes, size),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -309,22 +137,19 @@ pub(super) fn start_dimmed(state: &ToggleState, event: &ModuleEvent) -> bool {
|
|||
&& !matches!(event, ModuleEvent::Command(c) if crate::app::commands::start_allowed(c))
|
||||
}
|
||||
|
||||
/// Label / glyph color for a possibly-dimmed tool.
|
||||
pub(super) const DIM_TOOL: Color = Color {
|
||||
r: 0.42,
|
||||
g: 0.42,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
/// `make_icon`, greyed out when `dim` (SVGs render monochrome via tint).
|
||||
pub(super) fn make_icon_dim(icon: IconKind, size: f32, dim: bool) -> Element<'static, Message> {
|
||||
if !dim {
|
||||
return make_icon(icon, size);
|
||||
}
|
||||
match icon {
|
||||
IconKind::Glyph(s) => text(s).size(size * 0.7).color(DIM_TOOL).into(),
|
||||
IconKind::Svg(bytes) => icons::tinted(bytes, size, DIM_TOOL),
|
||||
IconKind::Glyph(s) => text(s)
|
||||
.size(size * 0.7)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.42)),
|
||||
})
|
||||
.into(),
|
||||
IconKind::Svg(bytes) => icons::themed_disabled(bytes, size),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -349,15 +174,24 @@ pub(super) fn is_active_tool(
|
|||
|
||||
// ── Button style ───────────────────────────────────────────────────────────
|
||||
|
||||
pub(super) fn tool_btn_style(is_active: bool, status: button::Status) -> button::Style {
|
||||
pub(super) fn tool_btn_style(
|
||||
theme: &Theme,
|
||||
is_active: bool,
|
||||
status: button::Status,
|
||||
) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.primary.weak,
|
||||
(_, button::Status::Hovered) => palette.background.weak,
|
||||
(_, button::Status::Pressed) => palette.primary.weak,
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(match (is_active, status) {
|
||||
(true, _) => TOOL_ACTIVE,
|
||||
(_, button::Status::Hovered) => TOOL_HOVER,
|
||||
(_, button::Status::Pressed) => TOOL_ACTIVE,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: Color::WHITE,
|
||||
background: is_active
|
||||
.then_some(Background::Color(pair.color))
|
||||
.or_else(|| matches!(status, button::Status::Hovered | button::Status::Pressed)
|
||||
.then_some(Background::Color(pair.color))),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: Color::TRANSPARENT,
|
||||
|
|
@ -368,21 +202,93 @@ pub(super) fn tool_btn_style(is_active: bool, status: button::Status) -> button:
|
|||
}
|
||||
}
|
||||
|
||||
// ── Tooltip helpers ────────────────────────────────────────────────────────
|
||||
|
||||
pub(super) fn make_tip(tip: String) -> Element<'static, Message> {
|
||||
text(tip).size(11).color(Color::WHITE).into()
|
||||
pub(super) fn combo_btn_style(
|
||||
theme: &Theme,
|
||||
is_open: bool,
|
||||
status: button::Status,
|
||||
radius: f32,
|
||||
) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if is_open {
|
||||
palette.primary.weak
|
||||
} else if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
palette.background.weak
|
||||
} else {
|
||||
palette.background.weakest
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
radius: radius.into(),
|
||||
width: 1.0,
|
||||
color: if is_open {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
},
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tip_style(_theme: &Theme) -> container::Style {
|
||||
pub(super) fn popup_row_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
palette.background.weak
|
||||
} else {
|
||||
palette.background.base
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn popup_panel_style(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(TIP_BG)),
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: COMBO_BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(Color::WHITE),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn muted_text_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.72)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tool_label_style(theme: &Theme, dim: bool) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: dim.then_some(
|
||||
theme.extended_palette().background.base.text.scale_alpha(0.42),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tooltip helpers ────────────────────────────────────────────────────────
|
||||
|
||||
pub(super) fn make_tip(tip: String) -> Element<'static, Message> {
|
||||
text(tip).size(11).into()
|
||||
}
|
||||
|
||||
pub(super) fn tip_style(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -408,7 +314,7 @@ pub(super) fn render_small<'a>(
|
|||
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
||||
let btn = button(make_icon_dim(t.icon, SMALL_ICON, dim))
|
||||
.on_press(Message::RibbonToolClick { tool_id, event })
|
||||
.style(move |_: &Theme, status| tool_btn_style(active, status))
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
|
||||
.width(Length::Fixed(SMALL_W))
|
||||
.height(ROW_H)
|
||||
.padding([4, 4]);
|
||||
|
|
@ -474,31 +380,22 @@ pub(super) fn render_small<'a>(
|
|||
tool_id: last.to_string(),
|
||||
event: ModuleEvent::Command(last.to_string()),
|
||||
})
|
||||
.style(move |_: &Theme, status| tool_btn_style(active, status))
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
|
||||
.width(Length::Fixed(SMALL_W))
|
||||
.height(ROW_H)
|
||||
.padding([4, 4]);
|
||||
|
||||
let arr_tip = format!("{} options", cur_label);
|
||||
let arr_btn = button(
|
||||
container(icons::arrow_down(8.0, ARROW_COLOR))
|
||||
container(icons::themed_arrow_down(8.0))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => TOOL_HOVER,
|
||||
_ if dd_open => TOOL_ACTIVE,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, dd_open, status)
|
||||
})
|
||||
.width(Length::Fixed(ARROW_W))
|
||||
.height(ROW_H)
|
||||
|
|
@ -569,7 +466,9 @@ pub(super) fn render_large_dropdown<'a>(
|
|||
let top_btn = button(
|
||||
column![
|
||||
make_icon_dim(cur_icon, LARGE_ICON, dim),
|
||||
text(label.to_string()).size(10).color(if dim { DIM_TOOL } else { LABEL_COLOR }),
|
||||
text(label.to_string())
|
||||
.size(10)
|
||||
.style(move |theme: &Theme| tool_label_style(theme, dim)),
|
||||
]
|
||||
.align_x(iced::Center)
|
||||
.spacing(3),
|
||||
|
|
@ -578,7 +477,7 @@ pub(super) fn render_large_dropdown<'a>(
|
|||
tool_id: last.to_string(),
|
||||
event: ModuleEvent::Command(last.to_string()),
|
||||
})
|
||||
.style(move |_: &Theme, status| tool_btn_style(active, status))
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
|
||||
.width(Length::Fixed(LARGE_W))
|
||||
.height(Fill)
|
||||
.padding(Padding {
|
||||
|
|
@ -589,24 +488,15 @@ pub(super) fn render_large_dropdown<'a>(
|
|||
});
|
||||
|
||||
let arr_btn = button(
|
||||
container(icons::arrow_down(9.0, ARROW_COLOR))
|
||||
container(icons::themed_arrow_down(9.0))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => TOOL_HOVER,
|
||||
_ if dd_open => TOOL_ACTIVE,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, dd_open, status)
|
||||
})
|
||||
.width(Length::Fixed(LARGE_W))
|
||||
.height(LARGE_ARR)
|
||||
|
|
@ -659,13 +549,15 @@ pub(super) fn render_large<'a>(
|
|||
let btn = button(
|
||||
column![
|
||||
make_icon_dim(t.icon, LARGE_ICON, dim),
|
||||
text(t.label).size(10).color(if dim { DIM_TOOL } else { LABEL_COLOR }),
|
||||
text(t.label)
|
||||
.size(10)
|
||||
.style(move |theme: &Theme| tool_label_style(theme, dim)),
|
||||
]
|
||||
.align_x(iced::Center)
|
||||
.spacing(3),
|
||||
)
|
||||
.on_press(Message::RibbonToolClick { tool_id, event })
|
||||
.style(move |_: &Theme, status| tool_btn_style(active, status))
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
|
||||
.width(Length::Fixed(LARGE_W))
|
||||
.height(Fill)
|
||||
.padding(Padding {
|
||||
|
|
@ -737,10 +629,10 @@ pub(super) fn render_large<'a>(
|
|||
let freeze_icon = icons::raw(icons::layer_freeze(lf), 14.0);
|
||||
let lock_icon = icons::raw(icons::layer_lock(ll), 14.0);
|
||||
let swatch = container(text(""))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(lc)),
|
||||
border: Border {
|
||||
color: SWATCH_BORDER,
|
||||
color: theme.extended_palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 1.0.into(),
|
||||
},
|
||||
|
|
@ -758,27 +650,17 @@ pub(super) fn render_large<'a>(
|
|||
freeze_icon,
|
||||
lock_icon,
|
||||
swatch,
|
||||
container(text(active_layer).size(11).color(Color::WHITE))
|
||||
container(text(active_layer).size(11))
|
||||
.width(name_w)
|
||||
.clip(true),
|
||||
icons::arrow_down(9.0, COMBO_ARROW),
|
||||
icons::themed_arrow_down(9.0),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(LAYER_COMBO_ID.to_string()))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_open, status) {
|
||||
(true, _) => COMBO_OPEN_BG,
|
||||
(_, button::Status::Hovered) => COMBO_HOVER_BG,
|
||||
_ => COMBO_BG,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
width: 1.0,
|
||||
color: COMBO_BORDER,
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
combo_btn_style(theme, is_open, status, 3.0)
|
||||
})
|
||||
.padding([3, 8])
|
||||
.width(Fill);
|
||||
|
|
@ -793,20 +675,16 @@ pub(super) fn render_large<'a>(
|
|||
let event = t.event.clone();
|
||||
let icon_el: Element<Message> = if dim {
|
||||
make_icon_dim(t.icon, 16.0, true)
|
||||
} else { match t.icon {
|
||||
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
||||
IconKind::Svg(bytes) => {
|
||||
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
||||
.width(16)
|
||||
.height(16)
|
||||
.into()
|
||||
}
|
||||
} };
|
||||
} else {
|
||||
make_icon(t.icon, 16.0)
|
||||
};
|
||||
let msg = module_event_to_message(event);
|
||||
tooltip(
|
||||
button(icon_el)
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| tool_btn_style(is_active, status))
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, is_active, status)
|
||||
})
|
||||
.padding([2, 5]),
|
||||
make_tip(tip.to_string()),
|
||||
TipPos::Right,
|
||||
|
|
@ -864,7 +742,9 @@ pub(super) fn render_large<'a>(
|
|||
let mp_btn = button(
|
||||
column![
|
||||
make_icon_dim(match_prop.icon, LARGE_ICON, mp_dim),
|
||||
text(match_prop.label).size(10).color(if mp_dim { DIM_TOOL } else { LABEL_COLOR }),
|
||||
text(match_prop.label)
|
||||
.size(10)
|
||||
.style(move |theme: &Theme| tool_label_style(theme, mp_dim)),
|
||||
]
|
||||
.align_x(iced::Center)
|
||||
.spacing(3),
|
||||
|
|
@ -873,7 +753,7 @@ pub(super) fn render_large<'a>(
|
|||
tool_id: mp_id,
|
||||
event: mp_event,
|
||||
})
|
||||
.style(move |_: &Theme, status| tool_btn_style(mp_active, status))
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, mp_active, status))
|
||||
.width(Length::Fixed(LARGE_W))
|
||||
.height(Fill)
|
||||
.padding(Padding {
|
||||
|
|
@ -895,10 +775,10 @@ pub(super) fn render_large<'a>(
|
|||
let is_open = open_dd.as_deref() == Some(dd_id);
|
||||
let swatch_el: Element<'a, Message> = if let Some(c) = swatch {
|
||||
container(text(""))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(c)),
|
||||
border: Border {
|
||||
color: SWATCH_BORDER,
|
||||
color: theme.extended_palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 1.0.into(),
|
||||
},
|
||||
|
|
@ -913,40 +793,21 @@ pub(super) fn render_large<'a>(
|
|||
button(
|
||||
row![
|
||||
swatch_el,
|
||||
container(text(label).size(10).color(Color::WHITE))
|
||||
container(text(label).size(10))
|
||||
.width(Fill)
|
||||
.clip(true),
|
||||
icons::arrow_toggle(
|
||||
is_open,
|
||||
8.0,
|
||||
Color {
|
||||
r: 0.6,
|
||||
g: 0.6,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
},
|
||||
),
|
||||
if is_open {
|
||||
icons::themed_arrow_up(8.0)
|
||||
} else {
|
||||
icons::themed_arrow_down(8.0)
|
||||
},
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(dd_id.to_string()))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_open, status) {
|
||||
(true, _) => COMBO_OPEN_BG,
|
||||
(_, button::Status::Hovered) => COMBO_HOVER_BG,
|
||||
_ => COMBO_BG,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 1.0,
|
||||
color: if is_open {
|
||||
COMBO_ACTIVE_BORDER
|
||||
} else {
|
||||
COMBO_BORDER
|
||||
},
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
combo_btn_style(theme, is_open, status, 2.0)
|
||||
})
|
||||
.padding([3, 8])
|
||||
.width(Length::Fixed(PROP_W))
|
||||
|
|
@ -999,31 +860,21 @@ pub(super) fn render_large<'a>(
|
|||
// ── combo button ──
|
||||
let combo_btn = button(
|
||||
row![
|
||||
container(text(active.clone()).size(11).color(Color::WHITE))
|
||||
container(text(active.clone()).size(11))
|
||||
.width(Fill)
|
||||
.clip(true),
|
||||
icons::arrow_toggle(is_open, 9.0, COMBO_ARROW),
|
||||
if is_open {
|
||||
icons::themed_arrow_up(9.0)
|
||||
} else {
|
||||
icons::themed_arrow_down(9.0)
|
||||
},
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(combo_id.to_string()))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_open, status) {
|
||||
(true, _) => COMBO_OPEN_BG,
|
||||
(_, button::Status::Hovered) => COMBO_HOVER_BG,
|
||||
_ => COMBO_BG,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
width: 1.0,
|
||||
color: if is_open {
|
||||
COMBO_ACTIVE_BORDER
|
||||
} else {
|
||||
COMBO_BORDER
|
||||
},
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
combo_btn_style(theme, is_open, status, 3.0)
|
||||
})
|
||||
.padding([3, 8])
|
||||
.width(Fill);
|
||||
|
|
@ -1045,20 +896,16 @@ pub(super) fn render_large<'a>(
|
|||
let event = t.event.clone();
|
||||
let icon_el: Element<Message> = if dim {
|
||||
make_icon_dim(t.icon, 16.0, true)
|
||||
} else { match t.icon {
|
||||
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
||||
IconKind::Svg(bytes) => {
|
||||
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
||||
.width(16)
|
||||
.height(16)
|
||||
.into()
|
||||
}
|
||||
} };
|
||||
} else {
|
||||
make_icon(t.icon, 16.0)
|
||||
};
|
||||
let msg = module_event_to_message(event);
|
||||
tooltip(
|
||||
button(icon_el)
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| tool_btn_style(is_active, status))
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, is_active, status)
|
||||
})
|
||||
.padding([2, 5]),
|
||||
make_tip(tip.to_string()),
|
||||
TipPos::Right,
|
||||
|
|
@ -1128,7 +975,11 @@ pub(super) fn quick_access_btn<'a>(
|
|||
// they read on the dark top strip (raw black is invisible there).
|
||||
// On the Start tab, commands the start gate refuses render dimmed.
|
||||
let dim = is_start && !crate::app::commands::start_allowed(cmd);
|
||||
let icon = icons::tinted(icon_bytes, 16.0, if dim { DIM_TOOL } else { QA_ICON_COLOR });
|
||||
let icon = if dim {
|
||||
icons::themed_disabled(icon_bytes, 16.0)
|
||||
} else {
|
||||
icons::themed(icon_bytes, 16.0)
|
||||
};
|
||||
let btn = button(
|
||||
container(icon)
|
||||
.width(Fill)
|
||||
|
|
@ -1137,22 +988,7 @@ pub(super) fn quick_access_btn<'a>(
|
|||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::Command(cmd.to_string()))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Length::Fixed(TOP_HIST_W))
|
||||
.height(24)
|
||||
.padding([2, 0]);
|
||||
|
|
@ -1171,13 +1007,12 @@ pub(super) fn render_history_control<'a>(
|
|||
) -> Element<'a, Message> {
|
||||
let dd_open = open_dropdown.as_deref() == Some(dropdown_id);
|
||||
let active = count > 0;
|
||||
let icon_color = if active { Color::WHITE } else { LABEL_OFF };
|
||||
|
||||
let main_btn = {
|
||||
let glyph = if dropdown_id == UNDO_HISTORY_ID {
|
||||
icons::undo(15.0, icon_color)
|
||||
icons::themed_undo(15.0, active)
|
||||
} else {
|
||||
icons::redo(15.0, icon_color)
|
||||
icons::themed_redo(15.0, active)
|
||||
};
|
||||
let btn = button(
|
||||
container(glyph)
|
||||
|
|
@ -1186,7 +1021,9 @@ pub(super) fn render_history_control<'a>(
|
|||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(move |_: &Theme, status| top_hist_btn_style(active, dd_open, status))
|
||||
.style(move |theme: &Theme, status| {
|
||||
top_hist_btn_style(theme, active, dd_open, status)
|
||||
})
|
||||
.width(Length::Fixed(TOP_HIST_W))
|
||||
.height(24)
|
||||
.padding([2, 0]);
|
||||
|
|
@ -1211,16 +1048,19 @@ pub(super) fn render_history_control<'a>(
|
|||
|
||||
let arrow_btn = {
|
||||
let btn = button(
|
||||
container(icons::arrow_down(
|
||||
8.0,
|
||||
if active { ARROW_COLOR } else { LABEL_OFF },
|
||||
))
|
||||
container(if active {
|
||||
icons::themed_arrow_down(8.0)
|
||||
} else {
|
||||
icons::themed_disabled_arrow_down(8.0)
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(move |_: &Theme, status| top_hist_btn_style(active, dd_open, status))
|
||||
.style(move |theme: &Theme, status| {
|
||||
top_hist_btn_style(theme, active, dd_open, status)
|
||||
})
|
||||
.width(Length::Fixed(TOP_ARR_W))
|
||||
.height(24)
|
||||
.padding(0);
|
||||
|
|
@ -1243,19 +1083,26 @@ pub(super) fn render_history_control<'a>(
|
|||
}
|
||||
|
||||
pub(super) fn top_hist_btn_style(
|
||||
theme: &Theme,
|
||||
active: bool,
|
||||
open: bool,
|
||||
status: button::Status,
|
||||
) -> button::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (active, open, status) {
|
||||
(false, _, _) => palette.background.weakest,
|
||||
(_, true, _) => palette.primary.weak,
|
||||
(_, _, button::Status::Hovered) => palette.background.weak,
|
||||
(_, _, button::Status::Pressed) => palette.primary.weak,
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(match (active, open, status) {
|
||||
(false, _, _) => HIST_INACTIVE_BG,
|
||||
(_, true, _) => TOOL_ACTIVE,
|
||||
(_, _, button::Status::Hovered) => TOOL_HOVER,
|
||||
(_, _, button::Status::Pressed) => TOOL_ACTIVE,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: Color::WHITE,
|
||||
background: (!active || open || matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
))
|
||||
.then_some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: Color::TRANSPARENT,
|
||||
|
|
|
|||
|
|
@ -7,15 +7,12 @@
|
|||
//! module's tools can drive it. First used for paper-space viewport / plot
|
||||
//! actions; reusable for any future context action set.
|
||||
|
||||
use iced::widget::{button, column, container, svg, text, tooltip};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
use iced::widget::{button, column, container, text, tooltip};
|
||||
use iced::{Background, Border, Element, Length, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::modules::{IconKind, ToolDef};
|
||||
|
||||
const PANEL_BG: Color = Color { r: 0.13, g: 0.13, b: 0.13, a: 0.96 };
|
||||
const PANEL_BORDER: Color = Color { r: 0.32, g: 0.32, b: 0.32, a: 1.0 };
|
||||
const BTN_HOVER: Color = Color { r: 0.24, g: 0.24, b: 0.24, a: 1.0 };
|
||||
const ICON_SIZE: f32 = 22.0;
|
||||
const BTN_SIZE: f32 = 38.0;
|
||||
/// Gap between the toolbar and the right edge of the canvas.
|
||||
|
|
@ -23,25 +20,26 @@ const EDGE_MARGIN: f32 = 8.0;
|
|||
|
||||
fn icon_el(icon: IconKind) -> Element<'static, Message> {
|
||||
match icon {
|
||||
IconKind::Glyph(s) => text(s).size(ICON_SIZE * 0.85).color(Color::WHITE).into(),
|
||||
IconKind::Svg(bytes) => svg(svg::Handle::from_memory(bytes))
|
||||
.width(Length::Fixed(ICON_SIZE))
|
||||
.height(Length::Fixed(ICON_SIZE))
|
||||
.into(),
|
||||
IconKind::Glyph(s) => text(s).size(ICON_SIZE * 0.85).into(),
|
||||
IconKind::Svg(bytes) => crate::ui::icons::themed(bytes, ICON_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
fn tip_panel(label: &'static str) -> Element<'static, Message> {
|
||||
container(text(label).size(11).color(Color::WHITE))
|
||||
container(text(label).size(11))
|
||||
.padding([2, 6])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
@ -63,17 +61,22 @@ pub fn view(tools: &[ToolDef]) -> Option<Element<'static, Message>> {
|
|||
})
|
||||
.width(Length::Fixed(BTN_SIZE))
|
||||
.height(Length::Fixed(BTN_SIZE))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => BTN_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let hovered = matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
);
|
||||
button::Style {
|
||||
background: hovered
|
||||
.then_some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
text_color: palette.background.base.text,
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
// Label tooltip on the left so it never runs off the right edge.
|
||||
col = col.push(
|
||||
|
|
@ -81,14 +84,17 @@ pub fn view(tools: &[ToolDef]) -> Option<Element<'static, Message>> {
|
|||
);
|
||||
}
|
||||
|
||||
let panel = container(col).padding(4).style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
let panel = container(col).padding(4).style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: PANEL_BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
// Fill the canvas, pin the panel to the right edge and centre it
|
||||
|
|
|
|||
|
|
@ -120,22 +120,13 @@ impl StatusBar {
|
|||
|
||||
// Leftmost hamburger: opens a dropdown listing Model + every layout, so
|
||||
// a layout can be picked directly even when the tab strip is scrolled.
|
||||
let menu_button = button(crate::ui::icons::tinted(
|
||||
crate::ui::icons::MENU,
|
||||
16.0,
|
||||
if is_start { DISABLED_COLOR } else { ICON_COLOR },
|
||||
))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => PILL_BG,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
let menu_icon = if is_start {
|
||||
crate::ui::icons::themed_disabled(crate::ui::icons::MENU, 16.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::MENU, 16.0)
|
||||
};
|
||||
let menu_button = button(menu_icon)
|
||||
.style(button::subtle)
|
||||
.padding([4, 8]);
|
||||
let menu_btn = if is_start {
|
||||
tip(
|
||||
|
|
@ -156,15 +147,8 @@ impl StatusBar {
|
|||
)
|
||||
};
|
||||
|
||||
let add_button = button(text("+").size(12).color(if is_start {
|
||||
DISABLED_COLOR
|
||||
} else {
|
||||
ICON_COLOR
|
||||
}))
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
..Default::default()
|
||||
})
|
||||
let add_button = button(text("+").size(12))
|
||||
.style(button::subtle)
|
||||
.padding([4, 8]);
|
||||
let add_btn = if is_start {
|
||||
tip(
|
||||
|
|
@ -453,14 +437,17 @@ impl StatusBar {
|
|||
.justify_end(true);
|
||||
|
||||
container(wrap)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BAR_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
// One row matches the drawing (document) tab bar height so the three
|
||||
|
|
@ -501,19 +488,9 @@ fn format_coords(cursor: glam::DVec3, last: Option<glam::DVec3>, mode: i16, pick
|
|||
// ── Customization handle ──────────────────────────────────────────────────
|
||||
|
||||
fn customize_btn() -> Element<'static, Message> {
|
||||
button(crate::ui::icons::tinted(crate::ui::icons::MENU, 16.0, ICON_COLOR))
|
||||
button(crate::ui::icons::themed_secondary(crate::ui::icons::MENU, 16.0))
|
||||
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => PILL_BG,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
}
|
||||
|
|
@ -521,7 +498,7 @@ fn customize_btn() -> Element<'static, Message> {
|
|||
// ── Tooltip helper ────────────────────────────────────────────────────────
|
||||
|
||||
fn tip<'a>(content: Element<'a, Message>, label: &'static str) -> Element<'a, Message> {
|
||||
tip_node(content, text(label).size(11).color(Color::WHITE).into())
|
||||
tip_node(content, text(label).size(11).into())
|
||||
}
|
||||
|
||||
/// A menu root shows its tooltip until clicked. Moving from the root into the
|
||||
|
|
@ -549,26 +526,7 @@ fn tip_node<'a>(content: Element<'a, Message>, body: Element<'a, Message>) -> El
|
|||
tooltip(
|
||||
content,
|
||||
container(body)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 0.97,
|
||||
})),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(Color::WHITE),
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 8]),
|
||||
TipPos::Top,
|
||||
)
|
||||
|
|
@ -581,24 +539,26 @@ fn tip_node<'a>(content: Element<'a, Message>, body: Element<'a, Message>) -> El
|
|||
/// text labels were too small to read). The name lives in the tooltip each
|
||||
/// call site already wraps it with.
|
||||
fn toggle_pill(icon: &'static [u8], active: bool, msg: Message) -> Element<'static, Message> {
|
||||
let color = if active { OSNAP_ON_TEXT } else { OSNAP_OFF_TEXT };
|
||||
button(crate::ui::icons::tinted(icon, 17.0, color))
|
||||
let icon = if active {
|
||||
crate::ui::icons::themed_primary(icon, 17.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary(icon, 17.0)
|
||||
};
|
||||
button(icon)
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (active, status) {
|
||||
(true, button::Status::Hovered) => SNAP_ON_HOVER,
|
||||
(true, _) => SNAP_ON_BG,
|
||||
(false, button::Status::Hovered) => SNAP_OFF_HOVER,
|
||||
(false, _) => SNAP_OFF_BG,
|
||||
})),
|
||||
border: Border {
|
||||
color: if active { SNAP_BORDER_ON } else { BORDER_COLOR },
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: color,
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
.style(move |theme: &Theme, status| {
|
||||
let mut style = button::subtle(theme, status);
|
||||
if active {
|
||||
let palette = theme.extended_palette();
|
||||
style.background = Some(Background::Color(match status {
|
||||
button::Status::Hovered => palette.primary.base.color,
|
||||
_ => palette.primary.weak.color,
|
||||
}));
|
||||
style.text_color = palette.primary.weak.text;
|
||||
style.border.color = palette.primary.base.color;
|
||||
style.border.width = 1.0;
|
||||
}
|
||||
style
|
||||
})
|
||||
.padding([4, 7])
|
||||
.into()
|
||||
|
|
@ -611,16 +571,6 @@ fn toggle_pill(icon: &'static [u8], active: bool, msg: Message) -> Element<'stat
|
|||
// and a dropdown caret, so both halves read as a single integrated control and
|
||||
// the border / background / caret / sizing live in exactly ONE place.
|
||||
|
||||
/// Pill text/icon tint for the lit vs. dim state — shared so `main` (built by
|
||||
/// the caller) and the caret always match.
|
||||
fn pill_text_color(active: bool) -> Color {
|
||||
if active {
|
||||
OSNAP_ON_TEXT
|
||||
} else {
|
||||
OSNAP_OFF_TEXT
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a caller-built, already-click-wired `main` element together with its
|
||||
/// menu-bearing dropdown caret.
|
||||
fn split_pill<'a>(
|
||||
|
|
@ -628,22 +578,26 @@ fn split_pill<'a>(
|
|||
caret: Element<'a, Message>,
|
||||
active: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let bg = if active { SNAP_ON_BG } else { SNAP_OFF_BG };
|
||||
let border_color = if active {
|
||||
SNAP_BORDER_ON
|
||||
} else {
|
||||
BORDER_COLOR
|
||||
};
|
||||
|
||||
container(row![main, caret].spacing(3).align_y(iced::Center))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(if active {
|
||||
palette.primary.weak.color
|
||||
} else {
|
||||
palette.background.weakest.color
|
||||
})),
|
||||
border: Border {
|
||||
color: border_color,
|
||||
color: if active {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 6])
|
||||
.into()
|
||||
|
|
@ -664,7 +618,6 @@ fn polar_pill<'a>(
|
|||
let tooltip_text = format!(
|
||||
"Polar Tracking ({angle})\nF10 — left-click on/off\nRight-click cycles · ▾ picks angle",
|
||||
);
|
||||
let color = pill_text_color(active);
|
||||
|
||||
// Right-click quick-cycles through the same increments the picker lists.
|
||||
const CYCLE: &[f32] = &[90.0, 45.0, 30.0, 22.5, 18.0, 15.0, 10.0, 5.0, 1.0];
|
||||
|
|
@ -673,10 +626,24 @@ fn polar_pill<'a>(
|
|||
None => 45.0,
|
||||
};
|
||||
|
||||
let polar_icon = if active {
|
||||
crate::ui::icons::themed_primary(crate::ui::icons::ST_POLAR, 17.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::ST_POLAR, 17.0)
|
||||
};
|
||||
let main = mouse_area(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::ST_POLAR, 17.0, color),
|
||||
text(angle).size(11).color(color),
|
||||
polar_icon,
|
||||
text(angle).size(11).style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
text::Style {
|
||||
color: Some(if active {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.base.text.scale_alpha(0.72)
|
||||
}),
|
||||
}
|
||||
}),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -685,26 +652,8 @@ fn polar_pill<'a>(
|
|||
.on_right_press(Message::SetPolarAngle(next_angle));
|
||||
let main = tooltip(
|
||||
main,
|
||||
container(text(tooltip_text).size(11).color(Color::WHITE))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 0.95,
|
||||
})),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
container(text(tooltip_text).size(11))
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 8]),
|
||||
TipPos::Top,
|
||||
);
|
||||
|
|
@ -712,7 +661,12 @@ fn polar_pill<'a>(
|
|||
let caret = status_menu::menu_bar(
|
||||
menu_tip(
|
||||
mouse_area(
|
||||
container(crate::ui::icons::arrow_down(9.0, color)).padding([4, 7]),
|
||||
container(if active {
|
||||
crate::ui::icons::themed_primary_arrow_down(9.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary_arrow_down(9.0)
|
||||
})
|
||||
.padding([4, 7]),
|
||||
)
|
||||
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||
.into(),
|
||||
|
|
@ -737,12 +691,13 @@ fn osnap_btn<'a>(
|
|||
entries: Vec<StatusMenuEntry<'a>>,
|
||||
) -> Element<'a, Message> {
|
||||
let on = active || snap_enabled;
|
||||
let snap_icon = if on {
|
||||
crate::ui::icons::themed_primary(crate::ui::icons::ST_OSNAP, 17.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::ST_OSNAP, 17.0)
|
||||
};
|
||||
let main = tip(
|
||||
mouse_area(crate::ui::icons::tinted(
|
||||
crate::ui::icons::ST_OSNAP,
|
||||
17.0,
|
||||
pill_text_color(on),
|
||||
))
|
||||
mouse_area(snap_icon)
|
||||
.on_press(Message::ToggleSnapEnabled)
|
||||
.into(),
|
||||
"Object Snap: toggle on/off\nF3",
|
||||
|
|
@ -751,7 +706,12 @@ fn osnap_btn<'a>(
|
|||
let caret = status_menu::menu_bar(
|
||||
menu_tip(
|
||||
mouse_area(
|
||||
container(crate::ui::icons::arrow_down(9.0, pill_text_color(on))).padding([4, 7]),
|
||||
container(if on {
|
||||
crate::ui::icons::themed_primary_arrow_down(9.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_secondary_arrow_down(9.0)
|
||||
})
|
||||
.padding([4, 7]),
|
||||
)
|
||||
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||
.into(),
|
||||
|
|
@ -768,44 +728,10 @@ fn osnap_btn<'a>(
|
|||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn layout_tab_context_menu(name: String) -> Element<'static, Message> {
|
||||
const MENU_BG: Color = Color {
|
||||
r: 0.17,
|
||||
g: 0.17,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const MENU_BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const ITEM_HOVER: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT_COLOR: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let item = |label: &'static str, msg: Message| {
|
||||
button(text(label).size(12).color(TEXT_COLOR))
|
||||
button(text(label).size(12))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => ITEM_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COLOR,
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([4, 12])
|
||||
.width(Length::Fill)
|
||||
};
|
||||
|
|
@ -818,15 +744,7 @@ fn layout_tab_context_menu(name: String) -> Element<'static, Message> {
|
|||
.spacing(0)
|
||||
.width(160),
|
||||
)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(MENU_BG)),
|
||||
border: Border {
|
||||
color: MENU_BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 0])
|
||||
.into()
|
||||
}
|
||||
|
|
@ -843,46 +761,34 @@ fn space_tab<'a>(
|
|||
enabled: bool,
|
||||
reorderable_layouts: Arc<[String]>,
|
||||
) -> Element<'a, Message> {
|
||||
let bg = move |is_active: bool, hovered: bool| {
|
||||
if is_active {
|
||||
TAB_ACTIVE
|
||||
} else if hovered {
|
||||
TAB_HOVER
|
||||
let tab_style = move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let text_color = if !enabled {
|
||||
palette.background.base.text.scale_alpha(0.42)
|
||||
} else if is_active {
|
||||
palette.primary.weak.text
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
}
|
||||
};
|
||||
|
||||
let border = Border {
|
||||
color: if is_active {
|
||||
ACCENT
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
width: if is_active { 1.0 } else { 0.0 },
|
||||
radius: 2.0.into(),
|
||||
};
|
||||
|
||||
let text_color = if !enabled {
|
||||
DISABLED_COLOR
|
||||
} else if is_active {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
palette.background.base.text.scale_alpha(0.72)
|
||||
};
|
||||
container::Style {
|
||||
background: is_active.then_some(Background::Color(palette.primary.weak.color)),
|
||||
border: Border {
|
||||
color: if is_active {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
width: if is_active { 1.0 } else { 0.0 },
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: Some(text_color),
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
if !enabled {
|
||||
let display = container(text(label.clone()).size(12).color(text_color))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg(is_active, false))),
|
||||
border,
|
||||
..Default::default()
|
||||
})
|
||||
let display = container(text(label.clone()).size(12))
|
||||
.style(tab_style)
|
||||
.padding([4, 10]);
|
||||
crate::ui::wrap_bar::PosReport::owned(
|
||||
format!("SB_LAYOUT_TAB:{label}"),
|
||||
|
|
@ -899,49 +805,15 @@ fn space_tab<'a>(
|
|||
.on_input(Message::LayoutRenameEdit)
|
||||
.on_submit(Message::LayoutRenameCommit)
|
||||
.size(12)
|
||||
.style(|_: &Theme, _| text_input::Style {
|
||||
background: Background::Color(TAB_ACTIVE),
|
||||
border: Border {
|
||||
color: ACCENT,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: Color {
|
||||
r: 0.5,
|
||||
g: 0.5,
|
||||
b: 0.5,
|
||||
a: 1.0,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.55,
|
||||
b: 0.90,
|
||||
a: 0.4,
|
||||
},
|
||||
})
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(90.0));
|
||||
|
||||
let cancel_btn = button(crate::ui::icons::tinted(
|
||||
let cancel_btn = button(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::CLOSE,
|
||||
10.0,
|
||||
Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
},
|
||||
))
|
||||
.on_press(Message::LayoutRenameCancel)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
border: Border::default(),
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([4, 4]);
|
||||
|
||||
row![input, cancel_btn]
|
||||
|
|
@ -951,12 +823,8 @@ fn space_tab<'a>(
|
|||
} else {
|
||||
// Normal clickable tab — left click switches. Paper-layout tabs are
|
||||
// wrapped in `ContextMenu`, which owns right-click handling.
|
||||
let display = container(text(label.clone()).size(12).color(text_color))
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg(is_active, false))),
|
||||
border,
|
||||
..Default::default()
|
||||
})
|
||||
let display = container(text(label.clone()).size(12))
|
||||
.style(tab_style)
|
||||
.padding([4, 10]);
|
||||
|
||||
let switch_msg = Message::LayoutSwitch(label.clone());
|
||||
|
|
@ -1006,34 +874,21 @@ fn space_mode_btn(current_layout: &str, in_mspace: bool) -> Element<'static, Mes
|
|||
("PAPER", false, Some(Message::MspaceCommand))
|
||||
};
|
||||
|
||||
let text_color = if active {
|
||||
SNAP_BORDER_ON
|
||||
} else {
|
||||
OSNAP_OFF_TEXT
|
||||
};
|
||||
let bg_normal = if active { SNAP_ON_BG } else { SNAP_OFF_BG };
|
||||
let bg_hover = if active {
|
||||
SNAP_ON_HOVER
|
||||
} else {
|
||||
SNAP_OFF_HOVER
|
||||
};
|
||||
let border_color = if active { SNAP_BORDER_ON } else { BORDER_COLOR };
|
||||
|
||||
let clickable = on_press.is_some();
|
||||
let mut btn = button(text(label).size(12).color(text_color))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered if clickable => bg_hover,
|
||||
_ => bg_normal,
|
||||
})),
|
||||
border: Border {
|
||||
color: border_color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color,
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
let mut btn = button(text(label).size(12))
|
||||
.style(move |theme: &Theme, status| {
|
||||
let mut style = button::subtle(theme, status);
|
||||
if active {
|
||||
let palette = theme.extended_palette();
|
||||
style.background = Some(Background::Color(match status {
|
||||
button::Status::Hovered if clickable => palette.primary.base.color,
|
||||
_ => palette.primary.weak.color,
|
||||
}));
|
||||
style.text_color = palette.primary.weak.text;
|
||||
style.border.color = palette.primary.base.color;
|
||||
style.border.width = 1.0;
|
||||
}
|
||||
style
|
||||
})
|
||||
.padding([4, 7]);
|
||||
|
||||
|
|
@ -1045,119 +900,12 @@ fn space_mode_btn(current_layout: &str, in_mspace: bool) -> Element<'static, Mes
|
|||
}
|
||||
|
||||
fn status_pill(label: impl Into<String>) -> Element<'static, Message> {
|
||||
container(text(label.into()).size(12).color(Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
}))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PILL_BG)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
container(text(label.into()).size(12))
|
||||
.style(container::bordered_box)
|
||||
.padding([4, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const BAR_BG: Color = Color {
|
||||
r: 0.14,
|
||||
g: 0.14,
|
||||
b: 0.14,
|
||||
a: 1.0,
|
||||
};
|
||||
const TAB_ACTIVE: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
};
|
||||
const TAB_HOVER: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const PILL_BG: Color = Color {
|
||||
r: 0.19,
|
||||
g: 0.19,
|
||||
b: 0.19,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COLOR: Color = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
};
|
||||
const ICON_COLOR: Color = Color {
|
||||
r: 0.70,
|
||||
g: 0.70,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const DISABLED_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.55,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
const OSNAP_ON_TEXT: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const OSNAP_OFF_TEXT: Color = Color {
|
||||
r: 0.42,
|
||||
g: 0.42,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
};
|
||||
const SNAP_ON_BG: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.20,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const SNAP_ON_HOVER: Color = Color {
|
||||
r: 0.14,
|
||||
g: 0.27,
|
||||
b: 0.42,
|
||||
a: 1.0,
|
||||
};
|
||||
const SNAP_BORDER_ON: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const SNAP_OFF_BG: Color = Color {
|
||||
r: 0.17,
|
||||
g: 0.17,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const SNAP_OFF_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
// ── Scale popup button ────────────────────────────────────────────────────
|
||||
|
||||
/// Shared visual root for labelled status-bar menus (units, scale, …).
|
||||
|
|
@ -1167,29 +915,9 @@ fn popup_pill(label: &str) -> Element<'static, Message> {
|
|||
|
||||
fn action_pill(label: &str, msg: Message) -> Element<'static, Message> {
|
||||
let label = label.to_string();
|
||||
button(
|
||||
text(label)
|
||||
.size(12)
|
||||
.color(OSNAP_OFF_TEXT),
|
||||
)
|
||||
button(text(label).size(12))
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => SNAP_ON_HOVER,
|
||||
_ => SNAP_OFF_BG,
|
||||
})),
|
||||
border: Border {
|
||||
color: match status {
|
||||
button::Status::Hovered => SNAP_BORDER_ON,
|
||||
_ => BORDER_COLOR,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
text_color: OSNAP_OFF_TEXT,
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
})
|
||||
.style(button::subtle)
|
||||
.padding([4, 7])
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,57 +51,30 @@ pub fn menu_bar<'a>(
|
|||
.safe_bounds_margin(0.0)
|
||||
.close_on_background_click_global(true)
|
||||
.draw_path(DrawPath::Backdrop)
|
||||
.style(|_: &Theme, _| iced_aw::style::menu_bar::Style {
|
||||
bar_background: Background::Color(Color::TRANSPARENT),
|
||||
bar_border: Border::default(),
|
||||
bar_shadow: Shadow::default(),
|
||||
menu_background: Background::Color(MENU_BG),
|
||||
menu_border: Border {
|
||||
color: MENU_BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
menu_shadow: Shadow {
|
||||
color: Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.35,
|
||||
.style(|theme: &Theme, _| {
|
||||
let palette = theme.extended_palette();
|
||||
iced_aw::style::menu_bar::Style {
|
||||
bar_background: Background::Color(Color::TRANSPARENT),
|
||||
bar_border: Border::default(),
|
||||
bar_shadow: Shadow::default(),
|
||||
menu_background: Background::Color(palette.background.weakest.color),
|
||||
menu_border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
offset: iced::Vector::new(0.0, -2.0),
|
||||
blur_radius: 6.0,
|
||||
},
|
||||
path: Background::Color(ACTIVE_BG),
|
||||
path_border: Border {
|
||||
color: ACTIVE_BORDER,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
menu_shadow: Shadow {
|
||||
color: palette.background.strongest.color.scale_alpha(0.35),
|
||||
offset: iced::Vector::new(0.0, -2.0),
|
||||
blur_radius: 6.0,
|
||||
},
|
||||
path: Background::Color(palette.primary.weak.color),
|
||||
path_border: Border {
|
||||
color: palette.primary.base.color,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
const MENU_BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const MENU_BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE_BG: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.20,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE_BORDER: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Status-bar customization and layout-list menu entries.
|
||||
|
||||
use iced::widget::{button, row, text};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
||||
|
|
@ -31,23 +31,17 @@ pub fn layout_entries<'a>(
|
|||
}
|
||||
|
||||
fn layout_row<'a>(name: String, is_current: bool) -> Element<'a, Message> {
|
||||
let lbl = text(name.clone())
|
||||
.size(11)
|
||||
.color(if is_current { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(name.clone()).size(11);
|
||||
button(row![lbl].align_y(iced::Center))
|
||||
.on_press(Message::LayoutSwitch(name))
|
||||
.style(move |_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_current, status) {
|
||||
(_, button::Status::Hovered) => ROW_HOVER,
|
||||
(true, _) => Color {
|
||||
r: 0.18,
|
||||
g: 0.26,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
let mut style = button::subtle(theme, status);
|
||||
if is_current && status == button::Status::Active {
|
||||
let palette = theme.extended_palette();
|
||||
style.background = Some(Background::Color(palette.primary.weak.color));
|
||||
style.text_color = palette.primary.weak.text;
|
||||
}
|
||||
style
|
||||
})
|
||||
.width(Fill)
|
||||
.padding([4, 12])
|
||||
|
|
@ -55,51 +49,16 @@ fn layout_row<'a>(name: String, is_current: bool) -> Element<'a, Message> {
|
|||
}
|
||||
|
||||
fn menu_row(label: &'static str, checked: bool, msg: Message) -> Element<'static, Message> {
|
||||
let check = crate::ui::icons::check_cell(checked, CHECK_COLOR);
|
||||
let check = crate::ui::icons::themed_check_cell(checked);
|
||||
|
||||
let lbl = text(label)
|
||||
.size(11)
|
||||
.color(if checked { LABEL_ON } else { LABEL_OFF });
|
||||
let lbl = text(label).size(11);
|
||||
|
||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||
|
||||
button(content)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => ROW_HOVER,
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::subtle)
|
||||
.width(Fill)
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
|
||||
const ROW_HOVER: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
};
|
||||
const CHECK_COLOR: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.75,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_ON: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
const LABEL_OFF: Color = Color {
|
||||
r: 0.65,
|
||||
g: 0.65,
|
||||
b: 0.65,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,16 +7,9 @@
|
|||
//! the style / scale managers' frame so it looks consistent.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::style::style_manager::{hdivider, tb_button, BG, BORDER, DIM, LIST, TB, TEXT};
|
||||
use crate::ui::style::style_manager::{hdivider, muted_text_style, tb_button};
|
||||
use iced::widget::{column, container, mouse_area, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const MEMBER_CHECK: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.82,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
/// `scales` is `(name, "paper:drawing" ratio, is_member)`. Every label is cloned
|
||||
/// into the widget tree, so the returned element borrows nothing from the args.
|
||||
|
|
@ -26,17 +19,17 @@ pub fn view_window(
|
|||
) -> Element<'static, Message> {
|
||||
let toolbar = container(
|
||||
row![
|
||||
text(format!("Object: {object_label}"))
|
||||
.size(11)
|
||||
.color(TEXT),
|
||||
text(format!("Object: {object_label}")).size(11),
|
||||
Space::new().width(Fill),
|
||||
tb_button("Close", Message::CloseModal, true),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -45,21 +38,17 @@ pub fn view_window(
|
|||
let rows: Vec<Element<'_, Message>> = scales
|
||||
.iter()
|
||||
.map(|(name, ratio, member)| {
|
||||
let check = crate::ui::icons::check_cell(*member, MEMBER_CHECK);
|
||||
let check = crate::ui::icons::themed_check_cell(*member);
|
||||
let label = row![
|
||||
check,
|
||||
text(name.clone()).size(11).color(TEXT).width(Fill),
|
||||
text(ratio.clone()).size(10).color(DIM),
|
||||
text(name.clone()).size(11).width(Fill),
|
||||
text(ratio.clone()).size(10).style(muted_text_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
let cell = container(label)
|
||||
.padding([4, 8])
|
||||
.width(Fill)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
text_color: Some(TEXT),
|
||||
..Default::default()
|
||||
});
|
||||
.width(Fill);
|
||||
mouse_area(cell)
|
||||
.on_press(Message::AnnoObjectScaleToggle(name.clone()))
|
||||
.into()
|
||||
|
|
@ -67,14 +56,17 @@ pub fn view_window(
|
|||
.collect();
|
||||
|
||||
let list = container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -84,7 +76,7 @@ pub fn view_window(
|
|||
column![
|
||||
text("Click a scale to add or remove the object's representation for it.")
|
||||
.size(10)
|
||||
.color(DIM),
|
||||
.style(muted_text_style),
|
||||
list,
|
||||
]
|
||||
.spacing(6)
|
||||
|
|
@ -95,8 +87,10 @@ pub fn view_window(
|
|||
.padding(12);
|
||||
|
||||
container(column![toolbar, hdivider(), body])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -4,44 +4,7 @@ use crate::app::{ColorPickTarget, DsField, Message};
|
|||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const FIELD: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
/// All DimStyle field values needed by the view.
|
||||
pub struct DimStyleValues<'a> {
|
||||
|
|
@ -131,44 +94,57 @@ pub struct DimStyleValues<'a> {
|
|||
}
|
||||
|
||||
fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (active, st) {
|
||||
(true, _) => ACTIVE,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (active, st) {
|
||||
(true, _) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
palette.background.strong
|
||||
}
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: ACCENT,
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,8 +152,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -221,7 +199,7 @@ pub fn view_window<'a>(
|
|||
]
|
||||
.spacing(2);
|
||||
|
||||
let lbl = |s: &'static str| text(s).size(11).color(DIM).width(180);
|
||||
let lbl = |s: &'static str| text(s).size(11).style(muted_style).width(180);
|
||||
|
||||
let mk_field = |fld: DsField, val: &'a str| -> Element<'a, Message> {
|
||||
text_input("", val)
|
||||
|
|
@ -338,7 +316,7 @@ pub fn view_window<'a>(
|
|||
|
||||
let tab_content: Element<'_, Message> = match tab {
|
||||
0 => column![
|
||||
text("Dimension Line").size(11).color(ACCENT),
|
||||
text("Dimension Line").size(11).style(primary_style),
|
||||
row![
|
||||
lbl("Extension (DIMDLE)"),
|
||||
mk_field(DsField::Dimdle, vals.dimdle)
|
||||
|
|
@ -359,7 +337,7 @@ pub fn view_window<'a>(
|
|||
.align_y(iced::Center),
|
||||
chk("Suppress 1st line (DIMSD1)", vals.dimsd1, DsField::Dimsd1),
|
||||
chk("Suppress 2nd line (DIMSD2)", vals.dimsd2, DsField::Dimsd2),
|
||||
text("Extension Line").size(11).color(ACCENT),
|
||||
text("Extension Line").size(11).style(primary_style),
|
||||
row![
|
||||
lbl("Extension (DIMEXE)"),
|
||||
mk_field(DsField::Dimexe, vals.dimexe)
|
||||
|
|
@ -421,7 +399,7 @@ pub fn view_window<'a>(
|
|||
.spacing(7)
|
||||
.into(),
|
||||
1 => column![
|
||||
text("Arrows").size(11).color(ACCENT),
|
||||
text("Arrows").size(11).style(primary_style),
|
||||
hrow(
|
||||
"Arrowhead (DIMBLK)",
|
||||
vals.block_opts.clone(),
|
||||
|
|
@ -485,7 +463,7 @@ pub fn view_window<'a>(
|
|||
.spacing(7)
|
||||
.into(),
|
||||
2 => column![
|
||||
text("Text").size(11).color(ACCENT),
|
||||
text("Text").size(11).style(primary_style),
|
||||
row![
|
||||
lbl("Height (DIMTXT)"),
|
||||
mk_field(DsField::Dimtxt, vals.dimtxt)
|
||||
|
|
@ -551,7 +529,7 @@ pub fn view_window<'a>(
|
|||
.spacing(7)
|
||||
.into(),
|
||||
3 => column![
|
||||
text("Scale").size(11).color(ACCENT),
|
||||
text("Scale").size(11).style(primary_style),
|
||||
chk("Annotative", vals.annotative, DsField::Annotative),
|
||||
row![
|
||||
lbl("Overall scale (DIMSCALE)"),
|
||||
|
|
@ -565,7 +543,7 @@ pub fn view_window<'a>(
|
|||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
text("Units").size(11).color(ACCENT),
|
||||
text("Units").size(11).style(primary_style),
|
||||
enum_field("Format (DIMLUNIT)", DsField::Dimlunit, vals.dimlunit, OPT_LUNIT),
|
||||
row![
|
||||
lbl("Decimals (DIMDEC)"),
|
||||
|
|
@ -636,7 +614,7 @@ pub fn view_window<'a>(
|
|||
("3", "Leading & trailing"),
|
||||
],
|
||||
),
|
||||
text("Fit").size(11).color(ACCENT),
|
||||
text("Fit").size(11).style(primary_style),
|
||||
enum_field(
|
||||
"Fit (DIMATFIT)",
|
||||
DsField::Dimatfit,
|
||||
|
|
@ -680,7 +658,7 @@ pub fn view_window<'a>(
|
|||
.spacing(7)
|
||||
.into(),
|
||||
5 => column![
|
||||
text("Alternate Units").size(11).color(ACCENT),
|
||||
text("Alternate Units").size(11).style(primary_style),
|
||||
chk(
|
||||
"Enable alternate units (DIMALT)",
|
||||
vals.dimalt,
|
||||
|
|
@ -728,7 +706,7 @@ pub fn view_window<'a>(
|
|||
.spacing(7)
|
||||
.into(),
|
||||
_ => column![
|
||||
text("Tolerances").size(11).color(ACCENT),
|
||||
text("Tolerances").size(11).style(primary_style),
|
||||
chk("Generate tolerances (DIMTOL)", vals.dimtol, DsField::Dimtol),
|
||||
chk("Limits generation (DIMLIM)", vals.dimlim, DsField::Dimlim),
|
||||
row![
|
||||
|
|
@ -773,7 +751,7 @@ pub fn view_window<'a>(
|
|||
// content container instead of the panel padding.
|
||||
let right_panel = container(
|
||||
column![
|
||||
text(format!("Editing: {selected}")).size(11).color(DIM),
|
||||
text(format!("Editing: {selected}")).size(11).style(muted_style),
|
||||
tabs,
|
||||
hdivider(),
|
||||
scrollable(container(tab_content).padding([12, 12]).width(Fill))
|
||||
|
|
|
|||
|
|
@ -4,63 +4,41 @@ use crate::app::Message;
|
|||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
(true, _) => ACCENT,
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
palette.background.strong
|
||||
}
|
||||
(true, _) => palette.primary.base,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +84,7 @@ pub struct MLeaderStyleView<'a> {
|
|||
}
|
||||
|
||||
fn section<'a>(label: &'static str) -> Element<'a, Message> {
|
||||
text(label).size(11).color(ACCENT).into()
|
||||
text(label).size(11).style(primary_style).into()
|
||||
}
|
||||
|
||||
fn num_row<'a>(
|
||||
|
|
@ -116,7 +94,7 @@ fn num_row<'a>(
|
|||
field: &'static str,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(150),
|
||||
text(label).size(11).style(muted_style).width(150),
|
||||
text_input(placeholder, value)
|
||||
.on_input(move |v| Message::MLeaderStyleEdit { field, value: v })
|
||||
.size(11)
|
||||
|
|
@ -150,7 +128,7 @@ fn color_row<'a>(
|
|||
Message::MLeaderColorMore(field),
|
||||
Message::OpenColorWindow(crate::app::ColorPickTarget::MLeader(field)),
|
||||
);
|
||||
row![text(label).size(11).color(DIM).width(150), selector]
|
||||
row![text(label).size(11).style(muted_style).width(150), selector]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
|
|
@ -163,7 +141,7 @@ fn enum_row<'a>(
|
|||
field: &'static str,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(150),
|
||||
text(label).size(11).style(muted_style).width(150),
|
||||
pick_list(options, Some(selected), move |value| {
|
||||
Message::MLeaderStyleSetEnum { field, value }
|
||||
})
|
||||
|
|
@ -178,7 +156,7 @@ fn enum_row<'a>(
|
|||
fn lineweight_row<'a>(selected: acadrust::types::LineWeight) -> Element<'a, Message> {
|
||||
let selected = crate::ui::properties::LwItem(selected);
|
||||
row![
|
||||
text("Line weight:").size(11).color(DIM).width(150),
|
||||
text("Line weight:").size(11).style(muted_style).width(150),
|
||||
pick_list(
|
||||
crate::ui::properties::lw_options(),
|
||||
Some(selected),
|
||||
|
|
@ -220,7 +198,7 @@ fn handle_row<'a>(
|
|||
field: &'static str,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(150),
|
||||
text(label).size(11).style(muted_style).width(150),
|
||||
pick_list(options, Some(selected), move |value| {
|
||||
Message::MLeaderStyleSetHandle { field, value }
|
||||
})
|
||||
|
|
@ -247,7 +225,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
|||
scrollable(
|
||||
column![
|
||||
row![
|
||||
text("Name:").size(11).color(DIM).width(150),
|
||||
text("Name:").size(11).style(muted_style).width(150),
|
||||
text(s.name.clone()).size(11),
|
||||
]
|
||||
.spacing(8),
|
||||
|
|
@ -422,7 +400,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
|||
.height(Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text("Select a style to view details.").size(11).color(DIM))
|
||||
container(text("Select a style to view details.").size(11).style(muted_style))
|
||||
.padding([12, 12])
|
||||
.into()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{column, container, row, scrollable, text};
|
||||
use iced::{Color, Element, Fill};
|
||||
use iced::{Element, Fill, Theme};
|
||||
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_window<'a>(
|
||||
styles: Vec<String>,
|
||||
|
|
@ -22,7 +21,7 @@ pub fn view_window<'a>(
|
|||
// ── Right: Details panel ──────────────────────────────────────────────
|
||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(120),
|
||||
text(label).size(11).style(muted_style).width(120),
|
||||
text(val).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
|
|
@ -49,7 +48,7 @@ pub fn view_window<'a>(
|
|||
&e.linetype
|
||||
};
|
||||
row![
|
||||
text(format!(" {idx}:")).size(10).color(DIM).width(24),
|
||||
text(format!(" {idx}:")).size(10).style(muted_style).width(24),
|
||||
text(format!("{:+.3}", e.offset)).size(10).width(70),
|
||||
text(color_str).size(10).width(90),
|
||||
text(lt).size(10),
|
||||
|
|
@ -65,7 +64,7 @@ pub fn view_window<'a>(
|
|||
info_row("Elements:", s.elements.len().to_string()),
|
||||
text(" Off Color Ltype")
|
||||
.size(10)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.into(),
|
||||
];
|
||||
col_items.extend(elem_rows);
|
||||
|
|
@ -73,7 +72,7 @@ pub fn view_window<'a>(
|
|||
.height(Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text("Select a style to view details.").size(11).color(DIM))
|
||||
container(text("Select a style to view details.").size(11).style(muted_style))
|
||||
.padding([12, 12])
|
||||
.into()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,108 +2,55 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const TB: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const FIELD: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
const LIST: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
(true, _) => ACCENT,
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
palette.background.strong
|
||||
}
|
||||
(true, _) => palette.primary.base,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: ACCENT,
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +58,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -122,8 +71,10 @@ fn vsep<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -156,13 +107,15 @@ pub fn view_window<'a>(
|
|||
.style(btn_s(false))
|
||||
.padding([4, 10]),
|
||||
Space::new().width(Fill),
|
||||
text(table_name).size(10).color(DIM),
|
||||
text(table_name).size(10).style(muted_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -199,19 +152,22 @@ pub fn view_window<'a>(
|
|||
};
|
||||
button(text(label).size(10).font(iced::Font::MONOSPACE))
|
||||
.on_press(Message::PlotStylePanelSelectAci(aci))
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (is_sel, st) {
|
||||
(true, _) => ACTIVE,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT,
|
||||
.style(move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (is_sel, st) {
|
||||
(true, _) => Some(palette.primary.strong),
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Some(palette.background.strong)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
button::Style {
|
||||
background: pair.map(|p| Background::Color(p.color)),
|
||||
text_color: pair
|
||||
.map(|p| p.text)
|
||||
.unwrap_or(palette.background.base.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([2, 8])
|
||||
.width(Fill)
|
||||
|
|
@ -221,16 +177,19 @@ pub fn view_window<'a>(
|
|||
|
||||
let aci_list = container(
|
||||
column![
|
||||
text("ACI Color Index").size(10).color(DIM),
|
||||
text("ACI Color Index").size(10).style(muted_style),
|
||||
container(scrollable(column(aci_items).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -269,12 +228,12 @@ pub fn view_window<'a>(
|
|||
.map(|e| format!("{}%", e.screening))
|
||||
.unwrap_or_else(|| "—".into());
|
||||
|
||||
let lbl = |s: &'static str| text(s).size(11).color(DIM);
|
||||
let lbl = |s: &'static str| text(s).size(11).style(muted_style);
|
||||
|
||||
let edit_panel = container(
|
||||
column![
|
||||
row![
|
||||
text("ACI:").size(11).color(DIM).width(100),
|
||||
text("ACI:").size(11).style(muted_style).width(100),
|
||||
text(format!("{selected_aci}")).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
|
|
@ -298,7 +257,7 @@ pub fn view_window<'a>(
|
|||
.size(11)
|
||||
.padding([4, 8]),
|
||||
Space::new().height(8),
|
||||
text("Current values:").size(10).color(DIM),
|
||||
text("Current values:").size(10).style(muted_style),
|
||||
text(format!(" Color: {cur_color}")).size(10),
|
||||
text(format!(" Lineweight: {cur_lw}")).size(10),
|
||||
text(format!(" Screening: {cur_scr}")).size(10),
|
||||
|
|
@ -318,8 +277,10 @@ pub fn view_window<'a>(
|
|||
let body = row![aci_list, vsep(), edit_panel].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -7,15 +7,7 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, canvas, column, container, radio, row, text, text_input, Space};
|
||||
use iced::{mouse, Background, Border, Color, Element, Length, Point, Rectangle, Size, Theme};
|
||||
|
||||
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
||||
const WHITE: Color = Color { r: 0.92, g: 0.92, b: 0.92, a: 1.0 };
|
||||
const DIM: Color = Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 };
|
||||
const ACCENT: Color = Color { r: 0.30, g: 0.62, b: 0.95, a: 1.0 };
|
||||
const CELL: Color = Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 };
|
||||
const CELL_SEL: Color = Color { r: 0.30, g: 0.62, b: 0.95, a: 1.0 };
|
||||
const GLYPH: Color = Color { r: 0.90, g: 0.90, b: 0.90, a: 1.0 };
|
||||
use iced::{mouse, Background, Border, Element, Length, Point, Rectangle, Size, Theme};
|
||||
|
||||
const CELL_PX: f32 = 44.0;
|
||||
|
||||
|
|
@ -37,22 +29,23 @@ impl canvas::Program<Message> for GlyphCanvas {
|
|||
&self,
|
||||
_state: &(),
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
||||
let glyph = theme.extended_palette().background.base.text;
|
||||
let (cx, cy) = (bounds.width * 0.5, bounds.height * 0.5);
|
||||
let r = bounds.width.min(bounds.height) * 0.30;
|
||||
let stroke = canvas::Stroke {
|
||||
width: 1.4,
|
||||
style: canvas::Style::Solid(GLYPH),
|
||||
style: canvas::Style::Solid(glyph),
|
||||
..Default::default()
|
||||
};
|
||||
let line = |a: Point, b: Point| canvas::Path::line(a, b);
|
||||
|
||||
match self.mode & 0x0F {
|
||||
0 => frame.fill(&canvas::Path::circle(Point::new(cx, cy), 2.4), GLYPH),
|
||||
0 => frame.fill(&canvas::Path::circle(Point::new(cx, cy), 2.4), glyph),
|
||||
1 => {}
|
||||
2 => {
|
||||
frame.stroke(&line(Point::new(cx - r, cy), Point::new(cx + r, cy)), stroke.clone());
|
||||
|
|
@ -89,39 +82,52 @@ fn cell<'a>(value: i16, selected: bool) -> Element<'a, Message> {
|
|||
button(glyph)
|
||||
.padding(0)
|
||||
.on_press(Message::PointStyleSetMode(value))
|
||||
.style(move |_: &Theme, status| {
|
||||
let bg = if selected {
|
||||
CELL_SEL
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if selected {
|
||||
palette.primary.strong
|
||||
} else if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 }
|
||||
palette.background.strong
|
||||
} else {
|
||||
CELL
|
||||
palette.background.weak
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(Color { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: Color { r: 0.3, g: 0.3, b: 0.3, a: 1.0 },
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
icon: DIM,
|
||||
placeholder: DIM,
|
||||
value: WHITE,
|
||||
selection: ACCENT,
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +144,7 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
|||
}
|
||||
|
||||
let size_row = row![
|
||||
text("Point Size:").size(13).color(WHITE),
|
||||
text("Point Size:").size(13),
|
||||
Space::new().width(10),
|
||||
text_input("0", size_buf)
|
||||
.on_input(Message::PointStyleSizeInput)
|
||||
|
|
@ -147,7 +153,7 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
|||
.size(13)
|
||||
.width(110),
|
||||
Space::new().width(6),
|
||||
text(if relative { "%" } else { "units" }).size(12).color(DIM),
|
||||
text(if relative { "%" } else { "units" }).size(12).style(muted_style),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
|
||||
|
|
@ -171,26 +177,14 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
|||
]
|
||||
.spacing(6);
|
||||
|
||||
let ok = button(text("OK").size(13).color(WHITE))
|
||||
let ok = button(text("OK").size(13))
|
||||
.padding([5, 22])
|
||||
.on_press(Message::PointStyleOk)
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: 0.32, g: 0.55, b: 0.85, a: 1.0 }
|
||||
} else {
|
||||
ACCENT
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::primary);
|
||||
|
||||
container(
|
||||
column![
|
||||
text("Point Style").size(18).color(WHITE),
|
||||
text("Point Style").size(18),
|
||||
Space::new().height(6),
|
||||
grid,
|
||||
Space::new().height(12),
|
||||
|
|
@ -203,8 +197,10 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
|||
.spacing(4)
|
||||
.padding(20),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -6,53 +6,36 @@
|
|||
//! messages instead of the StyleKind machinery.
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::ui::style::style_manager::{hdivider, tb_button, vsep, BG, BORDER, DIM, LIST, TB, TEXT};
|
||||
use crate::ui::style::style_manager::{
|
||||
hdivider, muted_text_style, tb_button, vsep,
|
||||
};
|
||||
use iced::widget::{
|
||||
column, container, mouse_area, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
/// Inline-rename text-input id, so the rename-start handler can focus it.
|
||||
pub fn rename_input_id() -> iced::widget::Id {
|
||||
iced::widget::Id::new("scale-rename-input")
|
||||
}
|
||||
|
||||
const INPUT_BG: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const CURRENT_CHECK: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.82,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(INPUT_BG),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.46,
|
||||
b: 0.80,
|
||||
a: 0.45,
|
||||
},
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,8 +63,10 @@ pub fn view_window<'a, 'b>(
|
|||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -105,21 +90,24 @@ pub fn view_window<'a, 'b>(
|
|||
}
|
||||
let is_sel = name.as_str() == selected;
|
||||
let is_cur = name.eq_ignore_ascii_case(current);
|
||||
let check = crate::ui::icons::check_cell(is_cur, CURRENT_CHECK);
|
||||
let check = crate::ui::icons::themed_check_cell(is_cur);
|
||||
let label = row![
|
||||
check,
|
||||
text(name.clone()).size(11).color(TEXT).width(Fill),
|
||||
text(ratio.clone()).size(10).color(DIM),
|
||||
text(name.clone()).size(11).width(Fill),
|
||||
text(ratio.clone()).size(10).style(muted_text_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
let cell = container(label)
|
||||
.padding([4, 8])
|
||||
.width(Fill)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: is_sel.then_some(Background::Color(ACTIVE)),
|
||||
text_color: Some(TEXT),
|
||||
.style(move |theme: &Theme| {
|
||||
let pair = theme.extended_palette().primary.strong;
|
||||
container::Style {
|
||||
background: is_sel.then_some(Background::Color(pair.color)),
|
||||
text_color: is_sel.then_some(pair.text),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
mouse_area(cell)
|
||||
.on_press(Message::ScaleManagerSelect(name.clone()))
|
||||
|
|
@ -130,16 +118,19 @@ pub fn view_window<'a, 'b>(
|
|||
|
||||
let list_panel = container(
|
||||
column![
|
||||
text("Scales").size(10).color(DIM),
|
||||
text("Scales").size(10).style(muted_text_style),
|
||||
container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -161,7 +152,7 @@ pub fn view_window<'a, 'b>(
|
|||
let field =
|
||||
|label: &'static str, ph: &'static str, value: &str, on: fn(String) -> Message| {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(96),
|
||||
text(label).size(11).style(muted_text_style).width(96),
|
||||
text_input(ph, value)
|
||||
.on_input(on)
|
||||
.style(field_style)
|
||||
|
|
@ -175,13 +166,13 @@ pub fn view_window<'a, 'b>(
|
|||
|
||||
let editor = container(
|
||||
column![
|
||||
text("Scale").size(10).color(DIM),
|
||||
text("Scale").size(10).style(muted_text_style),
|
||||
field("Paper units", "1", paper_buf, Message::ScaleManagerPaperBuf),
|
||||
field("Drawing units", "50", drawing_buf, Message::ScaleManagerDrawingBuf),
|
||||
Space::new().height(6),
|
||||
text("Double-click a scale to rename it; edit its paper : drawing ratio here. New / Copy add a scale. Changes are kept only if you click Apply before closing.")
|
||||
.size(10)
|
||||
.color(DIM),
|
||||
.style(muted_text_style),
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
|
|
@ -192,8 +183,10 @@ pub fn view_window<'a, 'b>(
|
|||
let body = row![list_panel, vsep(), editor].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
use crate::app::{Message, StyleKind};
|
||||
use iced::widget::{container, mouse_area, row, text, text_input};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
|
||||
/// Shared id for the inline rename field, so the rename-start handler can focus
|
||||
/// it the moment the row turns editable.
|
||||
|
|
@ -18,25 +18,6 @@ pub fn rename_input_id() -> iced::widget::Id {
|
|||
iced::widget::Id::new("style-rename-input")
|
||||
}
|
||||
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const CURRENT_CHECK: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.82,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
/// One row of the style list. Renders an editable `text_input` when `name` is
|
||||
/// the style being renamed (`rename_active`), otherwise a selectable row whose
|
||||
/// double click starts the rename. The current style gets a green ✓.
|
||||
|
|
@ -61,16 +42,19 @@ pub fn item<'a>(
|
|||
} else {
|
||||
// Fixed-width ✓ column keeps every name left-aligned whether or not the
|
||||
// row is current.
|
||||
let check = crate::ui::icons::check_cell(is_current, CURRENT_CHECK);
|
||||
let label = row![check, text(name.to_string()).size(11).color(TEXT)]
|
||||
let check = crate::ui::icons::themed_check_cell(is_current);
|
||||
let label = row![check, text(name.to_string()).size(11)]
|
||||
.align_y(iced::Center);
|
||||
let cell = container(label)
|
||||
.padding([4, 8])
|
||||
.width(Fill)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: is_selected.then_some(Background::Color(ACTIVE)),
|
||||
text_color: Some(TEXT),
|
||||
.style(move |theme: &Theme| {
|
||||
let pair = theme.extended_palette().primary.strong;
|
||||
container::Style {
|
||||
background: is_selected.then_some(Background::Color(pair.color)),
|
||||
text_color: is_selected.then_some(pair.text),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
mouse_area(cell)
|
||||
.on_press(on_select)
|
||||
|
|
|
|||
|
|
@ -10,52 +10,7 @@
|
|||
use crate::app::{Message, StyleKind};
|
||||
use iced::widget::button::{Status, Style};
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
// Shared palette — also reused by the scale manager so it matches the style
|
||||
// managers exactly ([[feedback_shared_infra]]).
|
||||
pub(crate) const TB: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(crate) const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(crate) const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(crate) const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(crate) const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
pub(crate) const LIST: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
/// Everything the shared frame needs. The per-manager `editor` element is the
|
||||
/// only bespoke part.
|
||||
|
|
@ -103,8 +58,10 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
|||
.spacing(4)
|
||||
.align_y(iced::Center);
|
||||
let toolbar = container(bar)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -131,16 +88,19 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
|||
|
||||
let list_panel = container(
|
||||
column![
|
||||
text("Styles").size(10).color(DIM),
|
||||
text("Styles").size(10).style(muted_text_style),
|
||||
container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -161,8 +121,10 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
|||
let body = row![list_panel, vsep(), s.editor].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -182,35 +144,30 @@ pub(crate) fn tb_button<'a>(label: &'a str, msg: Message, accent: bool) -> Eleme
|
|||
}
|
||||
|
||||
fn btn_s(accent: bool) -> impl Fn(&Theme, Status) -> Style {
|
||||
move |_: &Theme, st| Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, Status::Hovered | Status::Pressed) => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
(false, Status::Hovered | Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
(true, _) => ACCENT,
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (accent, st) {
|
||||
(true, Status::Hovered | Status::Pressed) => palette.primary.strong,
|
||||
(false, Status::Hovered | Status::Pressed) => palette.background.strong,
|
||||
(true, _) => palette.primary.base,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn muted_text_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,8 +175,10 @@ pub(crate) fn hdivider<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -229,8 +188,10 @@ pub(crate) fn vsep<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -4,63 +4,41 @@ use crate::app::Message;
|
|||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Column,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
(true, _) => ACCENT,
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
palette.background.strong
|
||||
}
|
||||
(true, _) => palette.primary.base,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +67,7 @@ pub fn view_window<'a>(
|
|||
// ── Right: Details panel ──────────────────────────────────────────────
|
||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(160),
|
||||
text(label).size(11).style(muted_style).width(160),
|
||||
text(val).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
|
|
@ -108,7 +86,7 @@ pub fn view_window<'a>(
|
|||
field: &'static str|
|
||||
-> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(150),
|
||||
text(label).size(11).style(muted_style).width(150),
|
||||
text_input(placeholder, value)
|
||||
.on_input(move |v| Message::TableStyleCellEdit {
|
||||
row,
|
||||
|
|
@ -142,14 +120,14 @@ pub fn view_window<'a>(
|
|||
Message::TableColorMore(row, field),
|
||||
Message::OpenColorWindow(crate::app::ColorPickTarget::Table(row, field)),
|
||||
);
|
||||
row![text(label).size(11).color(DIM).width(150), selector]
|
||||
row![text(label).size(11).style(muted_style).width(150), selector]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
};
|
||||
let mut col = Column::new()
|
||||
.spacing(3)
|
||||
.push(text(row_label).size(11).color(ACCENT))
|
||||
.push(text(row_label).size(11).style(primary_style))
|
||||
.push(cell_in(
|
||||
" Text style:",
|
||||
"Standard",
|
||||
|
|
@ -161,7 +139,7 @@ pub fn view_window<'a>(
|
|||
.push(cell_color(" Fill color:", &cell_fillcolor[r], "fillcolor"))
|
||||
.push(
|
||||
row![
|
||||
text(" Alignment:").size(11).color(DIM).width(150),
|
||||
text(" Alignment:").size(11).style(muted_style).width(150),
|
||||
pick_list(
|
||||
[
|
||||
"TopLeft",
|
||||
|
|
@ -199,7 +177,7 @@ pub fn view_window<'a>(
|
|||
.push(
|
||||
text(" Borders (type / weight / color / spacing / hidden)")
|
||||
.size(10)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
);
|
||||
|
||||
let borders: [(&'static str, &acadrust::objects::TableCellBorder); 6] = [
|
||||
|
|
@ -214,7 +192,7 @@ pub fn view_window<'a>(
|
|||
let bu = b as u8;
|
||||
col = col.push(
|
||||
row![
|
||||
text(format!(" {bname}")).size(11).color(DIM).width(28),
|
||||
text(format!(" {bname}")).size(11).style(muted_style).width(28),
|
||||
pick_list(
|
||||
["Single", "Double"]
|
||||
.iter()
|
||||
|
|
@ -282,7 +260,7 @@ pub fn view_window<'a>(
|
|||
column![
|
||||
info_row("Name:", s.name.clone()),
|
||||
row![
|
||||
text("Description:").size(11).color(DIM).width(160),
|
||||
text("Description:").size(11).style(muted_style).width(160),
|
||||
text_input("", description_buf)
|
||||
.on_input(|v| Message::TableStyleEdit {
|
||||
field: "description",
|
||||
|
|
@ -294,7 +272,7 @@ pub fn view_window<'a>(
|
|||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text("Flow direction:").size(11).color(DIM).width(160),
|
||||
text("Flow direction:").size(11).style(muted_style).width(160),
|
||||
pick_list(
|
||||
["Down", "Up"]
|
||||
.iter()
|
||||
|
|
@ -314,7 +292,7 @@ pub fn view_window<'a>(
|
|||
.size(14)
|
||||
.text_size(11),
|
||||
row![
|
||||
text("H Margin:").size(11).color(DIM).width(160),
|
||||
text("H Margin:").size(11).style(muted_style).width(160),
|
||||
text_input("1.5", hmargin_buf)
|
||||
.on_input(|v| Message::TableStyleEdit {
|
||||
field: "hmargin",
|
||||
|
|
@ -326,7 +304,7 @@ pub fn view_window<'a>(
|
|||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text("V Margin:").size(11).color(DIM).width(160),
|
||||
text("V Margin:").size(11).style(muted_style).width(160),
|
||||
text_input("1.5", vmargin_buf)
|
||||
.on_input(|v| Message::TableStyleEdit {
|
||||
field: "vmargin",
|
||||
|
|
@ -362,7 +340,7 @@ pub fn view_window<'a>(
|
|||
.height(Fill)
|
||||
.into()
|
||||
} else {
|
||||
container(text("Select a style to view details.").size(11).color(DIM))
|
||||
container(text("Select a style to view details.").size(11).style(muted_style))
|
||||
.padding([12, 12])
|
||||
.into()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::app::StyleKind;
|
|||
use iced::widget::{
|
||||
button, canvas, checkbox, column, container, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{mouse, Background, Border, Color, Element, Fill, Length, Point, Rectangle, Theme};
|
||||
use iced::{mouse, Background, Border, Element, Fill, Length, Point, Rectangle, Theme};
|
||||
|
||||
/// View-model for the Text Style editor window.
|
||||
pub struct TextStyleView<'a> {
|
||||
|
|
@ -28,49 +28,6 @@ pub struct TextStyleView<'a> {
|
|||
pub rename_buf: &'a str,
|
||||
}
|
||||
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const FIELD: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
const LIST: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
const BUILTIN_FONTS: &[&str] = &[
|
||||
"Standard", "ISO", "Simplex", "RomanS", "RomanD", "RomanC", "RomanT", "ItalicC", "ItalicT",
|
||||
"ScriptS", "ScriptC", "GothGBT", "GothGRT", "GothITT", "GreekC", "Symbol",
|
||||
|
|
@ -78,34 +35,52 @@ const BUILTIN_FONTS: &[&str] = &[
|
|||
];
|
||||
|
||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (active, st) {
|
||||
(true, _) => ACTIVE,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT,
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (active, st) {
|
||||
(true, _) => Some(palette.primary.strong),
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Some(palette.background.strong)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
button::Style {
|
||||
background: pair.map(|p| Background::Color(p.color)),
|
||||
text_color: pair.map(|p| p.text).unwrap_or(palette.background.base.text),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD),
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: ACCENT,
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,8 +88,10 @@ fn vsep<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -143,7 +120,7 @@ impl canvas::Program<Message> for TextPreviewCanvas {
|
|||
&self,
|
||||
_state: &(),
|
||||
renderer: &iced::Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
|
|
@ -185,7 +162,7 @@ impl canvas::Program<Message> for TextPreviewCanvas {
|
|||
};
|
||||
let stroke = canvas::Stroke {
|
||||
width: 1.4,
|
||||
style: canvas::Style::Solid(TEXT),
|
||||
style: canvas::Style::Solid(theme.extended_palette().background.base.text),
|
||||
..Default::default()
|
||||
};
|
||||
for s in &strokes {
|
||||
|
|
@ -237,16 +214,19 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
|||
|
||||
let font_panel = container(
|
||||
column![
|
||||
text("Font File").size(10).color(DIM),
|
||||
text("Font File").size(10).style(muted_style),
|
||||
container(scrollable(column(font_items).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -275,7 +255,7 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
|||
field: &'static str,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(120),
|
||||
text(label).size(11).style(muted_style).width(120),
|
||||
text_input(ph, buf)
|
||||
.on_input(move |v| Message::TextStyleEdit { field, value: v })
|
||||
.style(field_style)
|
||||
|
|
@ -312,7 +292,7 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
|||
// ── Right: Properties ─────────────────────────────────────────────────
|
||||
let props_panel = container(
|
||||
column![
|
||||
text("Properties").size(11).color(ACCENT),
|
||||
text("Properties").size(11).style(primary_style),
|
||||
frow("Big Font:", "big-font file…", bigfont_buf, "bigfont"),
|
||||
frow("TrueType Font:", "e.g. Arial", ttf_buf, "ttf"),
|
||||
frow("Fixed Height:", "0 = variable", height_buf, "height"),
|
||||
|
|
@ -337,16 +317,19 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
|||
.size(15)
|
||||
.text_size(11),
|
||||
Space::new().height(8),
|
||||
text("Preview").size(10).color(DIM),
|
||||
text("Preview").size(10).style(muted_style),
|
||||
container(preview)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(FIELD)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(8)
|
||||
.width(Fill),
|
||||
|
|
@ -382,16 +365,19 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
|||
|
||||
let ttf_panel = container(
|
||||
column![
|
||||
text("TrueType (system)").size(10).color(DIM),
|
||||
text("TrueType (system)").size(10).style(muted_style),
|
||||
container(scrollable(column(ttf_items).spacing(1)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
|
|||
|
|
@ -1,48 +1,16 @@
|
|||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, text};
|
||||
use iced::{Background, Border, Color, Element, Theme};
|
||||
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const PANEL: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const WHITE: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
use iced::{Background, Element, Theme};
|
||||
|
||||
fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(100),
|
||||
text(value).size(11).color(WHITE),
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
})
|
||||
.width(100),
|
||||
text(value).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
|
|
@ -57,10 +25,16 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
|||
|
||||
let logo = container(
|
||||
column![
|
||||
text("Open CAD Studio").size(32).color(ACCENT),
|
||||
text("Open CAD Studio")
|
||||
.size(32)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}),
|
||||
text("CAD application for Architecture & Engineering")
|
||||
.size(11)
|
||||
.color(DIM),
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
|
|
@ -82,35 +56,11 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
|||
.spacing(2)
|
||||
.padding([12, 16]),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
.style(container::bordered_box);
|
||||
|
||||
let copy_btn = button(text("Copy Info").size(11))
|
||||
.on_press(Message::AboutCopyInfo)
|
||||
.style(|_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match st {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => ACCENT,
|
||||
})),
|
||||
text_color: WHITE,
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::primary)
|
||||
.padding([6, 16]);
|
||||
|
||||
let footer = row![copy_btn]
|
||||
|
|
@ -132,8 +82,10 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
|||
left: 20.0,
|
||||
}),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Background, Element, Fill, Length, Theme};
|
||||
|
||||
/// Which column of an alias row a text edit targets.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
@ -18,33 +18,21 @@ pub enum AliasField {
|
|||
/// Right-hand lane reserved for the scrollbar so it never overlaps the ✕ column.
|
||||
const GUTTER: f32 = 16.0;
|
||||
|
||||
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
||||
const FIELD_BG: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
|
||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const DIM: Color = Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 };
|
||||
const WHITE: Color = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
|
||||
const ADD_C: Color = Color { r: 0.20, g: 0.40, b: 0.62, a: 1.0 };
|
||||
|
||||
fn field_style(_: &Theme, _s: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD_BG),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
icon: WHITE,
|
||||
placeholder: DIM,
|
||||
value: WHITE,
|
||||
selection: Color { r: 0.25, g: 0.40, b: 0.60, a: 1.0 },
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the alias editor content. `rows` is the live working buffer.
|
||||
pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||
let title = text("Command Aliases").size(15).color(WHITE);
|
||||
let title = text("Command Aliases").size(15);
|
||||
let hint = text(
|
||||
"Type an alias and the command it runs (e.g. L → LINE). \
|
||||
Apply to save to ocad.pgp; closing discards unapplied edits.",
|
||||
)
|
||||
.size(11)
|
||||
.color(DIM);
|
||||
.style(muted_style);
|
||||
|
||||
// Right gutter reserved so the scrollbar has its own lane and never sits on
|
||||
// top of the row delete (✕) buttons. Applied to both the header and the
|
||||
|
|
@ -53,8 +41,8 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
|||
|
||||
let head = container(
|
||||
row![
|
||||
container(text("Alias").size(11).color(DIM)).width(Length::Fixed(120.0)),
|
||||
container(text("Command").size(11).color(DIM)).width(Fill),
|
||||
container(text("Alias").size(11).style(muted_style)).width(Length::Fixed(120.0)),
|
||||
container(text("Command").size(11).style(muted_style)).width(Fill),
|
||||
Space::new().width(Length::Fixed(30.0)),
|
||||
]
|
||||
.spacing(8),
|
||||
|
|
@ -65,31 +53,18 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
|||
for (idx, (alias, cmd)) in rows.iter().enumerate() {
|
||||
let alias_box = text_input("alias", alias)
|
||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Alias, value: v })
|
||||
.style(field_style)
|
||||
.size(13)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(120.0));
|
||||
let cmd_box = text_input("command", cmd)
|
||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Command, value: v })
|
||||
.style(field_style)
|
||||
.size(13)
|
||||
.padding([3, 6])
|
||||
.width(Fill);
|
||||
let del = button(crate::ui::icons::tinted(crate::ui::icons::CLOSE, 12.0, WHITE))
|
||||
let del = button(crate::ui::icons::themed_danger(crate::ui::icons::CLOSE, 12.0))
|
||||
.on_press(Message::AliasEditorRemove(idx))
|
||||
.padding([2, 6])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered) {
|
||||
Color { r: 0.45, g: 0.22, b: 0.22, a: 1.0 }
|
||||
} else {
|
||||
Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 }
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::danger);
|
||||
list = list.push(
|
||||
row![alias_box, cmd_box, del]
|
||||
.spacing(8)
|
||||
|
|
@ -97,40 +72,16 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
|||
);
|
||||
}
|
||||
|
||||
let add = button(text("+ Add alias").size(12).color(WHITE))
|
||||
let add = button(text("+ Add alias").size(12))
|
||||
.on_press(Message::AliasEditorAdd)
|
||||
.padding([4, 10])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered) {
|
||||
Color { r: 0.30, g: 0.30, b: 0.30, a: 1.0 }
|
||||
} else {
|
||||
Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 }
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::secondary);
|
||||
|
||||
// Apply — primary action; commits the rows to ocad.pgp and stays open.
|
||||
let apply = button(text("Apply").size(12).color(WHITE))
|
||||
let apply = button(text("Apply").size(12))
|
||||
.on_press(Message::AliasEditorApply)
|
||||
.padding([4, 16])
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: 0.24, g: 0.46, b: 0.74, a: 1.0 }
|
||||
} else {
|
||||
ADD_C
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
.style(button::primary);
|
||||
|
||||
container(
|
||||
column![
|
||||
|
|
@ -149,8 +100,10 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
|||
.padding(12)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -19,23 +19,10 @@ use acadrust::types::{Color as AcadColor, LineWeight};
|
|||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
use iced::{Background, Border, Element, Length, Theme};
|
||||
|
||||
use crate::ui::properties::{lw_options, LwItem};
|
||||
|
||||
// Palette shared with the style-manager windows so the editor reads as one of
|
||||
// them: toolbar (TB) on top, BG panels, ACTIVE for the selected tab / row.
|
||||
const TB: Color = Color { r: 0.13, g: 0.13, b: 0.13, a: 1.0 };
|
||||
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
||||
const WHITE: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 };
|
||||
const DIM: Color = Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 };
|
||||
const ACCENT: Color = Color { r: 0.25, g: 0.50, b: 0.85, a: 1.0 };
|
||||
const FIELD_BG: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
|
||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const ACTIVE: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 };
|
||||
const ROW_SEL: Color = ACTIVE;
|
||||
const ROW_BG: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
|
||||
|
||||
const LABEL_W: f32 = 120.0;
|
||||
|
||||
/// Which tab of the editor is showing.
|
||||
|
|
@ -146,28 +133,25 @@ pub fn color_from_label(label: &str) -> Option<AcadColor> {
|
|||
})
|
||||
}
|
||||
|
||||
fn field_style(_t: &Theme, _s: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD_BG),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
icon: WHITE,
|
||||
placeholder: DIM,
|
||||
value: WHITE,
|
||||
selection: ACCENT,
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn accent_btn(_t: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 }
|
||||
} else {
|
||||
ACCENT
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
text_input::Style {
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,8 +160,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(Length::Fill).height(1))
|
||||
.width(Length::Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -186,7 +172,7 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
|||
/// One `label : widget` row with a fixed-width label column.
|
||||
fn field_row<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
|
||||
row![
|
||||
container(text(label).size(12).color(DIM)).width(LABEL_W),
|
||||
container(text(label).size(12).style(muted_style)).width(LABEL_W),
|
||||
widget,
|
||||
]
|
||||
.spacing(8)
|
||||
|
|
@ -226,20 +212,28 @@ fn pick_field<'a>(
|
|||
|
||||
fn tab_button<'a>(label: &'a str, this: AttrTab, active: AttrTab) -> Element<'a, Message> {
|
||||
let is_active = this == active;
|
||||
button(text(label).size(11).color(WHITE))
|
||||
button(text(label).size(11))
|
||||
.padding([4, 12])
|
||||
.on_press(Message::AttrEditorTab(this))
|
||||
.style(move |_t: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match (is_active, status) {
|
||||
(true, _) => ACTIVE,
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 }
|
||||
palette.background.strong
|
||||
}
|
||||
_ => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 },
|
||||
})),
|
||||
text_color: WHITE,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
@ -259,20 +253,22 @@ pub fn view_window<'a>(
|
|||
) -> Element<'a, Message> {
|
||||
// ── Top toolbar: block name on the left, Apply on the right ───────────
|
||||
// Mirrors the style-manager windows (actions left, primary action right).
|
||||
let apply = button(text("Apply").size(11).color(WHITE))
|
||||
let apply = button(text("Apply").size(11))
|
||||
.padding([4, 14])
|
||||
.on_press(Message::AttrEditorApply)
|
||||
.style(accent_btn);
|
||||
.style(button::primary);
|
||||
let toolbar = container(
|
||||
row![
|
||||
text(format!("Block: {block}")).size(12).color(DIM),
|
||||
text(format!("Block: {block}")).size(12).style(muted_style),
|
||||
Space::new().width(Length::Fill),
|
||||
apply,
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
|
|
@ -286,7 +282,7 @@ pub fn view_window<'a>(
|
|||
.spacing(2);
|
||||
|
||||
let body: Element<'_, Message> = if rows.is_empty() {
|
||||
text("This block has no attributes.").size(13).color(DIM).into()
|
||||
text("This block has no attributes.").size(13).style(muted_style).into()
|
||||
} else {
|
||||
match tab {
|
||||
AttrTab::Attribute => attribute_tab(rows, selected),
|
||||
|
|
@ -303,8 +299,10 @@ pub fn view_window<'a>(
|
|||
.padding(12);
|
||||
|
||||
container(column![toolbar, hdivider(), content])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
|
|
@ -315,9 +313,9 @@ pub fn view_window<'a>(
|
|||
/// Attribute tab: tag / prompt / value list with row-select, plus a value box.
|
||||
fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Message> {
|
||||
let head = row![
|
||||
container(text("Tag").size(11).color(DIM)).width(130),
|
||||
container(text("Prompt").size(11).color(DIM)).width(Length::Fill),
|
||||
container(text("Value").size(11).color(DIM)).width(140),
|
||||
container(text("Tag").size(11).style(muted_style)).width(130),
|
||||
container(text("Prompt").size(11).style(muted_style)).width(Length::Fill),
|
||||
container(text("Value").size(11).style(muted_style)).width(140),
|
||||
]
|
||||
.spacing(6);
|
||||
|
||||
|
|
@ -325,27 +323,28 @@ fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Messag
|
|||
for (idx, r) in rows.iter().enumerate() {
|
||||
let is_sel = idx == selected;
|
||||
let line = row![
|
||||
container(text(r.tag.as_str()).size(12).color(WHITE)).width(130),
|
||||
container(text(r.prompt.as_str()).size(12).color(DIM)).width(Length::Fill),
|
||||
container(text(r.value.as_str()).size(12).color(WHITE)).width(140),
|
||||
container(text(r.tag.as_str()).size(12)).width(130),
|
||||
container(text(r.prompt.as_str()).size(12).style(muted_style)).width(Length::Fill),
|
||||
container(text(r.value.as_str()).size(12)).width(140),
|
||||
]
|
||||
.spacing(6);
|
||||
let btn = button(line)
|
||||
.on_press(Message::AttrEditorSelect(idx))
|
||||
.padding([3, 4])
|
||||
.width(Length::Fill)
|
||||
.style(move |_t: &Theme, status| {
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let hovered = matches!(status, button::Status::Hovered);
|
||||
let bg = if is_sel {
|
||||
ROW_SEL
|
||||
let pair = if is_sel {
|
||||
palette.primary.strong
|
||||
} else if hovered {
|
||||
Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 }
|
||||
palette.background.strong
|
||||
} else {
|
||||
ROW_BG
|
||||
palette.background.weak
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border::default(),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,35 @@ const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0;
|
|||
/// scrolled into view after it is added (#271).
|
||||
pub const LAYER_TABLE_SCROLL_ID: &str = "layer-manager-table-scroll";
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn table_input_style(
|
||||
theme: &Theme,
|
||||
status: iced::widget::text_input::Status,
|
||||
) -> iced::widget::text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
iced::widget::text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
iced::widget::text_input::Style {
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border {
|
||||
color: border,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Layer data ────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -313,39 +342,15 @@ impl LayerPanel {
|
|||
.size(FONT_SZ)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fixed(180.0))
|
||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
||||
background: Background::Color(Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
}),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.20,
|
||||
g: 0.44,
|
||||
b: 0.72,
|
||||
a: 0.5,
|
||||
},
|
||||
}),
|
||||
.style(table_input_style),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TOOLBAR_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -355,13 +360,15 @@ impl LayerPanel {
|
|||
let sc = self.sort_col;
|
||||
let sa = self.sort_asc;
|
||||
let mut header_row = row![
|
||||
text("Status").size(10).color(DIM).width(50),
|
||||
text("Status").size(10).style(muted_style).width(50),
|
||||
sortable_header("Name", LayerSortCol::Name, Length::Fixed(name_col_w), sc, sa),
|
||||
// Draggable divider: adjusts the Name column width (#359).
|
||||
iced::widget::mouse_area(
|
||||
container(iced::widget::Space::new().width(2).height(14)).style(
|
||||
|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER_COLOR)),
|
||||
|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
|
|
@ -390,20 +397,23 @@ impl LayerPanel {
|
|||
header_row = header_row.push(
|
||||
text(vp.label.as_str())
|
||||
.size(10)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(COL_ICON)),
|
||||
);
|
||||
}
|
||||
|
||||
let col_header = container(header_row)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(COL_HEADER_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
.width(Fill);
|
||||
|
|
@ -451,8 +461,10 @@ impl LayerPanel {
|
|||
|
||||
// ── Full-window frame ─────────────────────────────────────────────
|
||||
container(column![toolbar, col_header, table].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -478,28 +490,26 @@ fn sortable_header<'a>(
|
|||
active: Option<LayerSortCol>,
|
||||
asc: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let mut content = row![text(label).size(10).color(DIM)]
|
||||
let mut content = row![text(label).size(10).style(muted_style)]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center);
|
||||
if active == Some(col) {
|
||||
content = content.push(if asc {
|
||||
crate::ui::icons::arrow_up(8.0, DIM)
|
||||
crate::ui::icons::themed_arrow_up(8.0)
|
||||
} else {
|
||||
crate::ui::icons::arrow_down(8.0, DIM)
|
||||
crate::ui::icons::themed_arrow_down(8.0)
|
||||
});
|
||||
}
|
||||
button(content)
|
||||
.on_press(Message::LayerSort(col))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
)
|
||||
.then_some(Background::Color(
|
||||
theme.extended_palette().background.strong.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
|
|
@ -517,41 +527,31 @@ fn sortable_header<'a>(
|
|||
fn toolbar_btn<'a>(icon: &'static [u8], label: &'a str, msg: Message) -> Element<'a, Message> {
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::tinted(icon, 12.0, Color::WHITE),
|
||||
text(label).size(11).color(Color::WHITE),
|
||||
crate::ui::icons::themed(icon, 12.0),
|
||||
text(label).size(11),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(msg)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
},
|
||||
button::Status::Pressed => Color {
|
||||
r: 0.25,
|
||||
g: 0.25,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
.style(|theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => {
|
||||
palette.background.strong
|
||||
}
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
text_color: pair.text,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 10])
|
||||
.into()
|
||||
|
|
@ -563,46 +563,46 @@ fn toolbar_btn_cond<'a>(
|
|||
msg: Message,
|
||||
enabled: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let fg = if enabled {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
}
|
||||
};
|
||||
let mut b = button(
|
||||
row![
|
||||
crate::ui::icons::tinted(icon, 12.0, fg),
|
||||
text(label).size(11).color(fg),
|
||||
if enabled {
|
||||
crate::ui::icons::themed(icon, 12.0)
|
||||
} else {
|
||||
crate::ui::icons::themed_disabled(icon, 12.0)
|
||||
},
|
||||
if enabled {
|
||||
text(label).size(11)
|
||||
} else {
|
||||
text(label).size(11).style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(
|
||||
theme.extended_palette().background.base.text.scale_alpha(0.42)
|
||||
),
|
||||
})
|
||||
},
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match status {
|
||||
button::Status::Hovered if enabled => palette.background.strong,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
text_color: if enabled {
|
||||
pair.text
|
||||
} else {
|
||||
pair.text.scale_alpha(0.42)
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 10]);
|
||||
if enabled {
|
||||
|
|
@ -616,21 +616,25 @@ fn toolbar_btn_cond<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
/// Hover popup showing a layer's full name when the cell truncates it.
|
||||
fn name_tip<'a>(name: &'a str) -> Element<'a, Message> {
|
||||
container(text(name).size(FONT_SZ).color(ROW_TEXT))
|
||||
container(text(name).size(FONT_SZ))
|
||||
.padding(Padding {
|
||||
top: 3.0,
|
||||
bottom: 3.0,
|
||||
left: 7.0,
|
||||
right: 7.0,
|
||||
})
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.strong.color)),
|
||||
border: Border {
|
||||
color: BORDER_COLOR,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Some(palette.background.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
@ -655,16 +659,10 @@ fn layer_row<'a>(
|
|||
.height(ICON_SZ),
|
||||
)
|
||||
.on_press(on_press)
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(theme.extended_palette().background.strong.color)
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
|
|
@ -682,27 +680,9 @@ fn layer_row<'a>(
|
|||
let lck_svg = crate::ui::icons::layer_lock(layer.locked);
|
||||
|
||||
let status_dot: Element<'_, Message> = if is_current {
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::CHECK,
|
||||
13.0,
|
||||
Color {
|
||||
r: 0.25,
|
||||
g: 0.85,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
)
|
||||
crate::ui::icons::themed_success(crate::ui::icons::CHECK, 13.0)
|
||||
} else {
|
||||
crate::ui::icons::tinted(
|
||||
crate::ui::icons::DOT,
|
||||
9.0,
|
||||
Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
},
|
||||
)
|
||||
crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 9.0)
|
||||
};
|
||||
|
||||
// Name cell
|
||||
|
|
@ -717,38 +697,7 @@ fn layer_row<'a>(
|
|||
left: 4.0,
|
||||
right: 4.0,
|
||||
})
|
||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
||||
background: iced::Background::Color(Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
}),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 1.0,
|
||||
color: Color {
|
||||
r: 0.45,
|
||||
g: 0.65,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
},
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: Color {
|
||||
r: 0.4,
|
||||
g: 0.4,
|
||||
b: 0.4,
|
||||
a: 1.0,
|
||||
},
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.75,
|
||||
a: 0.5,
|
||||
},
|
||||
})
|
||||
.style(table_input_style)
|
||||
.width(Length::Fixed(name_col_w))
|
||||
.into()
|
||||
} else {
|
||||
|
|
@ -756,20 +705,13 @@ fn layer_row<'a>(
|
|||
let name_budget = ((name_col_w / 6.0) as usize).max(8);
|
||||
let name_btn = button(
|
||||
text(crate::ui::text_util::elide(&layer.name, name_budget))
|
||||
.size(FONT_SZ)
|
||||
.color(ROW_TEXT),
|
||||
.size(FONT_SZ),
|
||||
)
|
||||
.on_press(Message::LayerRenameStart(index))
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(status, button::Status::Hovered).then_some(
|
||||
Background::Color(theme.extended_palette().background.strong.color)
|
||||
),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
|
|
@ -837,7 +779,7 @@ fn layer_row<'a>(
|
|||
} else {
|
||||
text(layer.linetype.as_str())
|
||||
.size(FONT_SZ)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(COL_LT))
|
||||
.into()
|
||||
};
|
||||
|
|
@ -861,7 +803,7 @@ fn layer_row<'a>(
|
|||
} else {
|
||||
text(cur_lw_item.to_string())
|
||||
.size(FONT_SZ)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(COL_LW))
|
||||
.into()
|
||||
};
|
||||
|
|
@ -877,33 +819,13 @@ fn layer_row<'a>(
|
|||
left: 4.0,
|
||||
right: 4.0,
|
||||
})
|
||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
||||
background: iced::Background::Color(Color::TRANSPARENT),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 1.0,
|
||||
color: BORDER_COLOR,
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: DIM,
|
||||
value: ROW_TEXT,
|
||||
selection: Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.75,
|
||||
a: 0.5,
|
||||
},
|
||||
.style(|theme: &Theme, status| {
|
||||
let mut style = table_input_style(theme, status);
|
||||
style.background = iced::Background::Color(Color::TRANSPARENT);
|
||||
style
|
||||
})
|
||||
.width(Length::Fixed(COL_TRANS));
|
||||
|
||||
let bg = if is_selected {
|
||||
ROW_SEL
|
||||
} else if index % 2 == 0 {
|
||||
ROW_EVEN
|
||||
} else {
|
||||
ROW_ODD
|
||||
};
|
||||
|
||||
let mut row_content = row![
|
||||
container(status_dot)
|
||||
.width(50)
|
||||
|
|
@ -945,9 +867,20 @@ fn layer_row<'a>(
|
|||
|
||||
mouse_area(
|
||||
container(row_content)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if is_selected {
|
||||
palette.primary.weak
|
||||
} else if index % 2 == 0 {
|
||||
palette.background.base
|
||||
} else {
|
||||
palette.background.weak
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(Padding {
|
||||
top: 0.0,
|
||||
|
|
@ -965,31 +898,10 @@ fn layer_row<'a>(
|
|||
// ── Combo style ────────────────────────────────────────────────────────────
|
||||
|
||||
fn combo_input_style(
|
||||
_theme: &Theme,
|
||||
_status: iced::widget::text_input::Status,
|
||||
theme: &Theme,
|
||||
status: iced::widget::text_input::Status,
|
||||
) -> iced::widget::text_input::Style {
|
||||
iced::widget::text_input::Style {
|
||||
background: iced::Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
}),
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 1.0,
|
||||
color: BORDER_COLOR,
|
||||
},
|
||||
icon: Color::WHITE,
|
||||
placeholder: DIM,
|
||||
value: Color::WHITE,
|
||||
selection: Color {
|
||||
r: 0.25,
|
||||
g: 0.45,
|
||||
b: 0.75,
|
||||
a: 0.5,
|
||||
},
|
||||
}
|
||||
table_input_style(theme, status)
|
||||
}
|
||||
|
||||
// ── Display helpers ───────────────────────────────────────────────────────
|
||||
|
|
@ -1054,67 +966,3 @@ const COL_COLOR: f32 = 90.0;
|
|||
const COL_LT: f32 = 110.0;
|
||||
const COL_LW: f32 = 90.0;
|
||||
const COL_TRANS: f32 = 80.0;
|
||||
|
||||
// ── Colors ────────────────────────────────────────────────────────────────
|
||||
|
||||
const PANEL_BG: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
const TOOLBAR_BG: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 1.0,
|
||||
};
|
||||
const COL_HEADER_BG: Color = Color {
|
||||
r: 0.21,
|
||||
g: 0.21,
|
||||
b: 0.21,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_EVEN: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_ODD: Color = Color {
|
||||
r: 0.21,
|
||||
g: 0.21,
|
||||
b: 0.21,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_SEL: Color = Color {
|
||||
r: 0.18,
|
||||
g: 0.32,
|
||||
b: 0.52,
|
||||
a: 1.0,
|
||||
};
|
||||
const ROW_TEXT: Color = Color {
|
||||
r: 0.85,
|
||||
g: 0.85,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.50,
|
||||
g: 0.50,
|
||||
b: 0.50,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER_COLOR: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
#[allow(dead_code)]
|
||||
const ICON_COLOR: Color = Color {
|
||||
r: 0.80,
|
||||
g: 0.80,
|
||||
b: 0.80,
|
||||
a: 1.0,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,131 +2,37 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
|
||||
const TB: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.88,
|
||||
g: 0.88,
|
||||
b: 0.88,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACTIVE: Color = Color {
|
||||
r: 0.20,
|
||||
g: 0.40,
|
||||
b: 0.70,
|
||||
a: 1.0,
|
||||
};
|
||||
const FIELD: Color = Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 1.0,
|
||||
};
|
||||
const LIST: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const WARN: Color = Color {
|
||||
r: 0.80,
|
||||
g: 0.35,
|
||||
b: 0.25,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
},
|
||||
(true, _) => ACCENT,
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
move |theme: &Theme, status| {
|
||||
if accent {
|
||||
button::primary(theme, status)
|
||||
} else {
|
||||
button::secondary(theme, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (active, st) {
|
||||
(true, _) => ACTIVE,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.26,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: ACCENT,
|
||||
move |theme: &Theme, status| {
|
||||
if active {
|
||||
button::primary(theme, status)
|
||||
} else {
|
||||
button::subtle(theme, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,8 +40,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -145,8 +53,10 @@ fn vsep<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -169,34 +79,18 @@ pub fn view_window<'a>(
|
|||
.padding([4, 10]),
|
||||
button(text("Delete").size(11))
|
||||
.on_press(Message::LayoutManagerDelete)
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match st {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.60,
|
||||
g: 0.20,
|
||||
b: 0.18,
|
||||
a: 1.0
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0
|
||||
},
|
||||
})),
|
||||
text_color: if is_model { DIM } else { WARN },
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
.style(move |theme: &Theme, status| {
|
||||
if is_model {
|
||||
button::secondary(theme, status)
|
||||
} else {
|
||||
button::danger(theme, status)
|
||||
}
|
||||
})
|
||||
.padding([4, 10]),
|
||||
Space::new().width(Fill),
|
||||
button(
|
||||
row![
|
||||
crate::ui::icons::tinted(crate::ui::icons::TRI_LEFT_B, 9.0, Color::WHITE),
|
||||
crate::ui::icons::themed_arrow_left(9.0),
|
||||
text("Move Left").size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
|
|
@ -208,7 +102,7 @@ pub fn view_window<'a>(
|
|||
button(
|
||||
row![
|
||||
text("Move Right").size(11),
|
||||
crate::ui::icons::arrow_right(9.0, Color::WHITE),
|
||||
crate::ui::icons::themed_arrow_right(9.0),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
|
|
@ -224,8 +118,10 @@ pub fn view_window<'a>(
|
|||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -241,11 +137,7 @@ pub fn view_window<'a>(
|
|||
.spacing(5)
|
||||
.align_y(iced::Center);
|
||||
if is_cur {
|
||||
item_row = item_row.push(crate::ui::icons::tinted(
|
||||
crate::ui::icons::TRI_LEFT_B,
|
||||
8.0,
|
||||
Color::WHITE,
|
||||
));
|
||||
item_row = item_row.push(crate::ui::icons::themed_arrow_left(8.0));
|
||||
}
|
||||
button(item_row)
|
||||
.on_press(Message::LayoutManagerSelect(name.clone()))
|
||||
|
|
@ -258,17 +150,9 @@ pub fn view_window<'a>(
|
|||
|
||||
let layout_list = container(
|
||||
column![
|
||||
text("Layouts").size(10).color(DIM),
|
||||
text("Layouts").size(10).style(muted_style),
|
||||
container(scrollable(column(list_items).spacing(2)).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 3.0.into()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.padding(2),
|
||||
|
|
@ -293,38 +177,38 @@ pub fn view_window<'a>(
|
|||
} else {
|
||||
"Paper Space Layout"
|
||||
})
|
||||
.size(13)
|
||||
.color(TEXT),
|
||||
.size(13),
|
||||
Space::new().height(8),
|
||||
row![
|
||||
text("Name:").size(11).color(DIM).width(80),
|
||||
text("Name:").size(11).style(muted_style).width(80),
|
||||
text(selected).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![
|
||||
text("Status:").size(11).color(DIM).width(80),
|
||||
text("Status:").size(11).style(muted_style).width(80),
|
||||
text(if selected == current.as_str() {
|
||||
"Active"
|
||||
} else {
|
||||
"Inactive"
|
||||
})
|
||||
.size(11)
|
||||
.color(if selected == current.as_str() {
|
||||
ACCENT
|
||||
} else {
|
||||
DIM
|
||||
.style(move |theme: &Theme| {
|
||||
if selected == current.as_str() {
|
||||
primary_style(theme)
|
||||
} else {
|
||||
muted_style(theme)
|
||||
}
|
||||
}),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(16),
|
||||
text("Rename").size(10).color(DIM),
|
||||
text("Rename").size(10).style(muted_style),
|
||||
row![
|
||||
text_input("New name…", rename_buf)
|
||||
.on_input(Message::LayoutManagerRenameBuf)
|
||||
.on_submit(Message::LayoutManagerRenameCommit)
|
||||
.style(field_style)
|
||||
.size(11)
|
||||
.padding([4, 8]),
|
||||
button(text("OK").size(11))
|
||||
|
|
@ -343,8 +227,10 @@ pub fn view_window<'a>(
|
|||
let body = row![layout_list, vsep(), details].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use iced::time::Instant;
|
||||
use iced::widget::{button, column, container, row, stack, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::app::{
|
||||
|
|
@ -60,13 +60,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
|||
.width(Length::Fixed(fill_width))
|
||||
.height(Length::Fixed(BAR_TRACK_HEIGHT)),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.30,
|
||||
g: 0.62,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().primary.base.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
|
|
@ -86,13 +83,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
|||
let bar_track: Element<'_, Message> = container(
|
||||
stack![
|
||||
container(Space::new().width(Length::Fixed(BAR_TRACK_WIDTH)).height(Length::Fixed(BAR_TRACK_HEIGHT)))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.strong.color
|
||||
)),
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
..Default::default()
|
||||
|
|
@ -107,9 +101,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
|||
.into();
|
||||
|
||||
// ── Card body ────────────────────────────────────────────────────────
|
||||
let title = text("Opening file")
|
||||
.size(15)
|
||||
.color(Color::WHITE);
|
||||
let title = text("Opening file").size(15);
|
||||
|
||||
let name_line = text(format!(
|
||||
"{} ({})",
|
||||
|
|
@ -117,11 +109,8 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
|||
format_size(progress.size_bytes)
|
||||
))
|
||||
.size(13)
|
||||
.color(Color {
|
||||
r: 0.82,
|
||||
g: 0.82,
|
||||
b: 0.82,
|
||||
a: 1.0,
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.82)),
|
||||
});
|
||||
|
||||
let phase_line = text(format!(
|
||||
|
|
@ -130,52 +119,13 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
|||
basis_points as f32 / 100.0
|
||||
))
|
||||
.size(12)
|
||||
.color(Color {
|
||||
r: 0.70,
|
||||
g: 0.80,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
});
|
||||
|
||||
let cancel_btn: Element<'_, Message> = button(text("Cancel").size(12).color(Color::WHITE))
|
||||
let cancel_btn: Element<'_, Message> = button(text("Cancel").size(12))
|
||||
.on_press(Message::OpenCancel)
|
||||
.style(|_: &Theme, status| {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.32,
|
||||
a: 1.0,
|
||||
},
|
||||
button::Status::Pressed => Color {
|
||||
r: 0.42,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.40,
|
||||
g: 0.40,
|
||||
b: 0.40,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(button::danger)
|
||||
.padding([4, 14])
|
||||
.into();
|
||||
|
||||
|
|
@ -187,35 +137,25 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
|||
.width(Length::Fixed(CARD_WIDTH)),
|
||||
)
|
||||
.padding([18, 22])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 0.98,
|
||||
})),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.45,
|
||||
g: 0.45,
|
||||
b: 0.45,
|
||||
a: 1.0,
|
||||
},
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
// ── Backdrop (click-blocker + dim) ────────────────────────────────────
|
||||
let backdrop: Element<'_, Message> = container(Space::new().width(Fill).height(Fill))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 0.55,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.strong.color.scale_alpha(0.72)
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -1,96 +1,123 @@
|
|||
use crate::app::config::UiThemeConfig;
|
||||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, pick_list, row, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::widget::{
|
||||
button, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
pub fn view_window<'a>(default_save_format: &'a str) -> Element<'a, Message> {
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.17,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.32,
|
||||
g: 0.32,
|
||||
b: 0.36,
|
||||
a: 1.0,
|
||||
};
|
||||
const TEXT: Color = Color {
|
||||
r: 0.90,
|
||||
g: 0.90,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.60,
|
||||
g: 0.60,
|
||||
b: 0.64,
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
let selected = crate::io::SAVE_FORMAT_OPTIONS
|
||||
pub fn view_window<'a>(
|
||||
default_save_format: &'a str,
|
||||
ui_theme: &'a UiThemeConfig,
|
||||
theme_color_inputs: &'a [String; 6],
|
||||
) -> Element<'a, Message> {
|
||||
let selected_format = crate::io::SAVE_FORMAT_OPTIONS
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|candidate| *candidate == default_save_format);
|
||||
|
||||
let close = button(text("Close").size(12).color(TEXT))
|
||||
.on_press(Message::CloseModal)
|
||||
.padding([5, 16])
|
||||
.style(|_: &Theme, status| button::Style {
|
||||
background: Some(Background::Color(match status {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.34,
|
||||
g: 0.34,
|
||||
b: 0.38,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.26,
|
||||
g: 0.26,
|
||||
b: 0.29,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: TEXT,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let theme_options = Theme::ALL
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.chain(std::iter::once("Custom".to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
let selected_theme = Some(ui_theme.name.clone());
|
||||
|
||||
let body = column![
|
||||
text("Open and Save").size(15).color(TEXT),
|
||||
Space::new().height(16),
|
||||
let palette = ui_theme.palette.to_iced();
|
||||
let colors = [
|
||||
("Background", palette.background),
|
||||
("Text", palette.text),
|
||||
("Primary", palette.primary),
|
||||
("Success", palette.success),
|
||||
("Warning", palette.warning),
|
||||
("Danger", palette.danger),
|
||||
];
|
||||
|
||||
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.extended_palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
color_controls = color_controls.push(
|
||||
row![
|
||||
text(label).size(12).width(110),
|
||||
swatch,
|
||||
text_input("#RRGGBB", theme_color_inputs[index].as_str())
|
||||
.on_input(move |value| Message::OptionsThemeColorChanged(index, value))
|
||||
.width(130),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
|
||||
let close = button(text("Close").size(12))
|
||||
.on_press(Message::CloseModal)
|
||||
.padding([6, 18])
|
||||
.style(button::secondary);
|
||||
|
||||
let content = column![
|
||||
text("Open and Save").size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text("Default save format:").size(12).color(TEXT).width(150),
|
||||
pick_list(crate::io::SAVE_FORMAT_OPTIONS, selected, |format: &str| {
|
||||
Message::DefaultSaveFormatChanged(format.to_string())
|
||||
})
|
||||
text("Default save format:").size(12).width(150),
|
||||
pick_list(
|
||||
crate::io::SAVE_FORMAT_OPTIONS,
|
||||
selected_format,
|
||||
|format: &str| Message::DefaultSaveFormatChanged(format.to_string())
|
||||
)
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
Space::new().height(8),
|
||||
text(
|
||||
"Used when a new drawing is saved for the first time. Existing drawings keep their current file type and version."
|
||||
"Used for the first save of a new drawing. Existing drawings keep their file type and version."
|
||||
)
|
||||
.size(11)
|
||||
.color(DIM)
|
||||
.width(Fill),
|
||||
Space::new().height(Fill),
|
||||
row![Space::new().width(Fill), close],
|
||||
.size(11),
|
||||
Space::new().height(22),
|
||||
text("Theme").size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text("Iced theme:").size(12).width(150),
|
||||
pick_list(
|
||||
theme_options,
|
||||
selected_theme,
|
||||
Message::OptionsThemeChanged,
|
||||
)
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(8),
|
||||
text(
|
||||
"Changing a base colour switches to Custom. Iced generates every component shade from these six colours."
|
||||
)
|
||||
.size(11),
|
||||
Space::new().height(12),
|
||||
color_controls,
|
||||
]
|
||||
.spacing(0)
|
||||
.width(Fill);
|
||||
|
||||
let body = column![
|
||||
scrollable(content).height(Fill),
|
||||
Space::new().height(12),
|
||||
row![Space::new().width(Fill), close],
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
container(body)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::rounded_box)
|
||||
.padding([16, 18])
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
|
|||
|
|
@ -10,17 +10,7 @@ use iced::widget::{
|
|||
button, checkbox, column, container, mouse_area, pick_list, row, scrollable, text, text_input,
|
||||
Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
||||
|
||||
const TB: Color = Color { r: 0.13, g: 0.13, b: 0.13, a: 1.0 };
|
||||
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const TEXT: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 };
|
||||
const DIM: Color = Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 };
|
||||
const ACCENT: Color = Color { r: 0.25, g: 0.50, b: 0.85, a: 1.0 };
|
||||
const FIELD: Color = Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 };
|
||||
const ACTIVE: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 };
|
||||
const LIST: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
|
||||
/// Sentinel entries in the printer dropdown (not real printer names).
|
||||
pub const OUT_DEFAULT: &str = "System default printer";
|
||||
|
|
@ -221,32 +211,49 @@ impl PlotDialogState {
|
|||
}
|
||||
|
||||
fn btn(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||
move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 }
|
||||
}
|
||||
move |theme: &Theme, st| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match (accent, st) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||
Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 }
|
||||
palette.background.strong
|
||||
}
|
||||
(true, _) => ACCENT,
|
||||
_ => Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 },
|
||||
})),
|
||||
text_color: TEXT,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
(true, _) => palette.primary.base,
|
||||
_ => palette.background.weak,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_style(_: &Theme, _: text_input::Status) -> text_input::Style {
|
||||
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||
let palette = theme.extended_palette();
|
||||
let border = match status {
|
||||
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||
_ => palette.background.neutral.color,
|
||||
};
|
||||
text_input::Style {
|
||||
background: Background::Color(FIELD),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
icon: TEXT,
|
||||
placeholder: DIM,
|
||||
value: TEXT,
|
||||
selection: ACCENT,
|
||||
background: Background::Color(palette.background.base.color),
|
||||
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||
icon: palette.background.base.text,
|
||||
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||
}
|
||||
}
|
||||
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,23 +261,27 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
|||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn section_label<'a>(s: &'static str) -> Element<'a, Message> {
|
||||
text(s).size(11).color(DIM).into()
|
||||
text(s).size(11).style(muted_style).into()
|
||||
}
|
||||
|
||||
fn vsep<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(1).height(Fill))
|
||||
.width(1)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -295,13 +306,16 @@ fn setup_row<'a>(
|
|||
.into();
|
||||
}
|
||||
let is_sel = name == selected;
|
||||
let cell = container(text(name.to_string()).size(11).color(TEXT))
|
||||
let cell = container(text(name.to_string()).size(11))
|
||||
.padding([4, 8])
|
||||
.width(Fill)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: is_sel.then_some(Background::Color(ACTIVE)),
|
||||
text_color: Some(TEXT),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: is_sel.then_some(Background::Color(palette.primary.strong.color)),
|
||||
text_color: is_sel.then_some(palette.primary.strong.text),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
mouse_area(cell)
|
||||
.on_press(Message::PlotDlg(PlotDlgMsg::SelectSetup(name.to_string())))
|
||||
|
|
@ -321,7 +335,7 @@ fn drop_row<'a>(
|
|||
.text_size(12)
|
||||
.padding([3, 6])
|
||||
.width(Length::Fill);
|
||||
row![text(label).size(11).color(DIM).width(92), pl]
|
||||
row![text(label).size(11).style(muted_style).width(92), pl]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
|
|
@ -335,7 +349,7 @@ fn field_row<'a>(
|
|||
width: u16,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
text(label).size(11).color(DIM).width(92),
|
||||
text(label).size(11).style(muted_style).width(92),
|
||||
text_input("", value)
|
||||
.on_input(move |s| Message::PlotDlg(ctor(s)))
|
||||
.style(field_style)
|
||||
|
|
@ -413,8 +427,10 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
|||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weak.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -445,7 +461,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
|||
.map(|name| setup_row(name, &s.selected_setup, renaming, rename_buf))
|
||||
.collect();
|
||||
let list_body: Element<'_, Message> = if rows.is_empty() {
|
||||
container(text("(no page setups)").size(11).color(DIM))
|
||||
container(text("(no page setups)").size(11).style(muted_style))
|
||||
.padding([6, 8])
|
||||
.into()
|
||||
} else {
|
||||
|
|
@ -453,16 +469,19 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
|||
};
|
||||
let list_panel = container(
|
||||
column![
|
||||
text("Page setups").size(10).color(DIM),
|
||||
text("Page setups").size(10).style(muted_style),
|
||||
container(list_body)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(LIST)),
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
|
|
@ -550,11 +569,18 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
|||
hdivider(),
|
||||
section_label("Plot style table (pen assignments)"),
|
||||
row![
|
||||
container(text(style_label).size(12).color(TEXT))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(FIELD)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
container(text(style_label).size(12))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.base.color)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding([4, 8])
|
||||
.width(Fill),
|
||||
|
|
@ -583,7 +609,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
|||
.width(Length::Fill)
|
||||
)
|
||||
.width(Fill),
|
||||
text("DPI").size(11).color(DIM),
|
||||
text("DPI").size(11).style(muted_style),
|
||||
text_input("", &s.dpi)
|
||||
.on_input(move |v| Message::PlotDlg(PlotDlgMsg::Dpi(v)))
|
||||
.style(field_style)
|
||||
|
|
@ -631,8 +657,10 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
|||
let body = row![list_panel, vsep(), detail].height(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), body].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
use crate::app::Message;
|
||||
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
||||
use iced::widget::{button, column, container, pick_list, row, scrollable, text, text_input, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
/// Marketplace state passed to the Plugin Manager view.
|
||||
|
|
@ -25,124 +25,105 @@ inventory::submit!(crate::command::CommandRegistration {
|
|||
names: &["PLUGINS", "PLUGINMANAGER"]
|
||||
});
|
||||
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const CARD: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.62,
|
||||
b: 0.95,
|
||||
a: 1.0,
|
||||
};
|
||||
const WHITE: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
fn badge<'a>(label: String) -> Element<'a, Message> {
|
||||
container(text(label).size(11).color(WHITE))
|
||||
container(text(label).size(11))
|
||||
.padding([2, 8])
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.20,
|
||||
g: 0.34,
|
||||
b: 0.52,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| {
|
||||
let pair = theme.extended_palette().primary.weak;
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn toggle_button<'a>(id: &str, disabled: bool) -> Element<'a, Message> {
|
||||
// Label shows the action the click performs.
|
||||
let (label, on, off) = if disabled {
|
||||
("Enable", Color { r: 0.18, g: 0.5, b: 0.25, a: 1.0 }, Color { r: 0.22, g: 0.6, b: 0.3, a: 1.0 })
|
||||
let label = if disabled {
|
||||
"Enable"
|
||||
} else {
|
||||
("Disable", Color { r: 0.4, g: 0.22, b: 0.22, a: 1.0 }, Color { r: 0.55, g: 0.28, b: 0.28, a: 1.0 })
|
||||
"Disable"
|
||||
};
|
||||
let want_enabled = disabled; // clicking flips the state
|
||||
let id_owned = id.to_string();
|
||||
button(text(label).size(12).color(WHITE))
|
||||
button(text(label).size(12))
|
||||
.padding([3, 12])
|
||||
.on_press(Message::SetPluginEnabled(id_owned, want_enabled))
|
||||
.style(move |_: &Theme, status| {
|
||||
let bg = match status {
|
||||
button::Status::Hovered | button::Status::Pressed => off,
|
||||
_ => on,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(if disabled { button::success } else { button::danger })
|
||||
.into()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum StatusKind {
|
||||
Muted,
|
||||
Success,
|
||||
Danger,
|
||||
Warning,
|
||||
}
|
||||
|
||||
/// Coloured status pill for a discovered external package.
|
||||
fn status_badge<'a>(label: &str, color: Color) -> Element<'a, Message> {
|
||||
container(text(label.to_string()).size(11).color(WHITE))
|
||||
fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
|
||||
container(text(label.to_string()).size(11))
|
||||
.padding([2, 8])
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = match kind {
|
||||
StatusKind::Muted => palette.background.weak,
|
||||
StatusKind::Success => palette.success.weak,
|
||||
StatusKind::Danger => palette.danger.weak,
|
||||
StatusKind::Warning => palette.warning.weak,
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
text_color: Some(pair.text),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Element<'a, Message> {
|
||||
let (status, color) = if loaded && disabled {
|
||||
("Disabled", Color { r: 0.45, g: 0.45, b: 0.45, a: 1.0 })
|
||||
let (status, kind) = if loaded && disabled {
|
||||
("Disabled", StatusKind::Muted)
|
||||
} else if loaded {
|
||||
("Loaded", Color { r: 0.2, g: 0.5, b: 0.3, a: 1.0 })
|
||||
("Loaded", StatusKind::Success)
|
||||
} else if !p.api_compatible() {
|
||||
("API incompatible", Color { r: 0.55, g: 0.28, b: 0.28, a: 1.0 })
|
||||
("API incompatible", StatusKind::Danger)
|
||||
} else if !p.lib_present {
|
||||
("No library", Color { r: 0.5, g: 0.42, b: 0.2, a: 1.0 })
|
||||
("No library", StatusKind::Warning)
|
||||
} else {
|
||||
("Restart to load", Color { r: 0.5, g: 0.42, b: 0.2, a: 1.0 })
|
||||
("Restart to load", StatusKind::Warning)
|
||||
};
|
||||
let mut header = row![
|
||||
text(p.name.clone()).size(15).color(WHITE),
|
||||
text(p.name.clone()).size(15),
|
||||
Space::new().width(8),
|
||||
badge(format!("v{}", p.version)),
|
||||
Space::new().width(8),
|
||||
badge(format!("API {}", p.api_version)),
|
||||
Space::new().width(Fill),
|
||||
status_badge(status, color),
|
||||
status_badge(status, kind),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
// A loaded plugin can be turned off (drops its ribbon tab + dispatch).
|
||||
|
|
@ -154,75 +135,52 @@ fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Elemen
|
|||
header = header.push(pill_button(
|
||||
"Uninstall",
|
||||
Message::PluginUninstall(p.id.clone()),
|
||||
Color { r: 0.4, g: 0.25, b: 0.25, a: 1.0 },
|
||||
button::danger,
|
||||
));
|
||||
|
||||
let id_line = text(p.id.clone()).size(11).color(ACCENT);
|
||||
let id_line = text(p.id.clone()).size(11).style(primary_style);
|
||||
let mut body = column![header, id_line].spacing(5);
|
||||
if !p.description.is_empty() {
|
||||
body = body.push(text(p.description.clone()).size(12).color(DIM));
|
||||
body = body.push(text(p.description.clone()).size(12).style(muted_style));
|
||||
}
|
||||
if !p.command_prefixes.is_empty() {
|
||||
body = body.push(
|
||||
text(format!("Commands: {}", p.command_prefixes.join(", ")))
|
||||
.size(11)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
);
|
||||
}
|
||||
container(body.padding([12, 14]))
|
||||
.width(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(CARD)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn pill_button<'a>(label: &str, msg: Message, bg: Color) -> Element<'a, Message> {
|
||||
button(text(label.to_string()).size(12).color(WHITE))
|
||||
fn pill_button<'a>(
|
||||
label: &str,
|
||||
msg: Message,
|
||||
style: fn(&Theme, button::Status) -> button::Style,
|
||||
) -> Element<'a, Message> {
|
||||
button(text(label.to_string()).size(12))
|
||||
.padding([4, 12])
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| {
|
||||
let c = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: bg.r + 0.08, g: bg.g + 0.08, b: bg.b + 0.08, a: 1.0 }
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(c)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(style)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Square icon variant of [`pill_button`] for glyph-free actions (e.g. remove).
|
||||
fn pill_icon_button<'a>(icon: &'static [u8], msg: Message, bg: Color) -> Element<'a, Message> {
|
||||
button(crate::ui::icons::tinted(icon, 11.0, WHITE))
|
||||
fn pill_icon_button<'a>(
|
||||
icon: &'static [u8],
|
||||
msg: Message,
|
||||
style: fn(&Theme, button::Status) -> button::Style,
|
||||
) -> Element<'a, Message> {
|
||||
button(crate::ui::icons::themed(icon, 11.0))
|
||||
.padding([5, 9])
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, status| {
|
||||
let c = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||
Color { r: bg.r + 0.08, g: bg.g + 0.08, b: bg.b + 0.08, a: 1.0 }
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(c)),
|
||||
text_color: WHITE,
|
||||
border: Border { radius: 4.0.into(), ..Default::default() },
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.style(style)
|
||||
.into()
|
||||
}
|
||||
|
||||
const GREEN: Color = Color { r: 0.2, g: 0.45, b: 0.28, a: 1.0 };
|
||||
const RED: Color = Color { r: 0.4, g: 0.25, b: 0.25, a: 1.0 };
|
||||
|
||||
/// Release dropdown + Install (+ optional unlink) for one repo.
|
||||
fn install_controls<'a>(
|
||||
repo: &str,
|
||||
|
|
@ -232,7 +190,7 @@ fn install_controls<'a>(
|
|||
) -> Element<'a, Message> {
|
||||
let repo_s = repo.to_string();
|
||||
let picker: Element<'_, Message> = if tags.is_empty() {
|
||||
text("no releases").size(11).color(DIM).into()
|
||||
text("no releases").size(11).style(muted_style).into()
|
||||
} else {
|
||||
let r = repo_s.clone();
|
||||
pick_list(tags, selected, move |tag| {
|
||||
|
|
@ -244,7 +202,11 @@ fn install_controls<'a>(
|
|||
let mut controls = row![
|
||||
picker,
|
||||
Space::new().width(8),
|
||||
pill_button("Install", Message::PluginInstall(repo_s.clone()), GREEN),
|
||||
pill_button(
|
||||
"Install",
|
||||
Message::PluginInstall(repo_s.clone()),
|
||||
button::success,
|
||||
),
|
||||
]
|
||||
.align_y(iced::Center)
|
||||
.spacing(4);
|
||||
|
|
@ -253,7 +215,7 @@ fn install_controls<'a>(
|
|||
controls = controls.push(pill_icon_button(
|
||||
crate::ui::icons::CLOSE,
|
||||
Message::PluginRepoRemove(repo_s),
|
||||
RED,
|
||||
button::danger,
|
||||
));
|
||||
}
|
||||
controls.into()
|
||||
|
|
@ -262,37 +224,33 @@ fn install_controls<'a>(
|
|||
fn market_card<'a>(body: iced::widget::Column<'a, Message>) -> Element<'a, Message> {
|
||||
container(body.spacing(4).padding([10, 12]))
|
||||
.width(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(CARD)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.style(container::bordered_box)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||
let mut col = column![text("Available plugins").size(13).color(ACCENT)].spacing(6);
|
||||
let mut col = column![text("Available plugins").size(13).style(primary_style)].spacing(6);
|
||||
|
||||
// Curated registry entries (from the OpenCADStudio repo).
|
||||
for e in m.registry {
|
||||
let tags = m.release_tags.get(&e.repo).cloned().unwrap_or_default();
|
||||
let selected = m.selected_tag.get(&e.repo).cloned();
|
||||
let header = row![
|
||||
text(e.name.clone()).size(14).color(WHITE),
|
||||
text(e.name.clone()).size(14),
|
||||
Space::new().width(Fill),
|
||||
install_controls(&e.repo, tags, selected, false),
|
||||
]
|
||||
.align_y(iced::Center);
|
||||
let mut body = column![header, text(e.repo.clone()).size(11).color(ACCENT)];
|
||||
let mut body = column![header, text(e.repo.clone()).size(11).style(primary_style)];
|
||||
if !e.description.is_empty() {
|
||||
body = body.push(text(e.description.clone()).size(12).color(DIM));
|
||||
body = body.push(text(e.description.clone()).size(12).style(muted_style));
|
||||
}
|
||||
col = col.push(market_card(body));
|
||||
}
|
||||
|
||||
// Manual: link any repo by owner/repo.
|
||||
col = col.push(Space::new().height(6));
|
||||
col = col.push(text("Add a repository").size(12).color(DIM));
|
||||
col = col.push(text("Add a repository").size(12).style(muted_style));
|
||||
col = col.push(
|
||||
row![
|
||||
text_input("owner/repo", m.input)
|
||||
|
|
@ -301,7 +259,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
|||
.size(13)
|
||||
.width(Fill),
|
||||
Space::new().width(8),
|
||||
pill_button("Add", Message::PluginRepoAdd, Color { r: 0.2, g: 0.4, b: 0.62, a: 1.0 }),
|
||||
pill_button("Add", Message::PluginRepoAdd, button::primary),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
|
|
@ -309,7 +267,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
|||
let tags = m.release_tags.get(repo).cloned().unwrap_or_default();
|
||||
let selected = m.selected_tag.get(repo).cloned();
|
||||
let header = row![
|
||||
text(repo.clone()).size(13).color(WHITE),
|
||||
text(repo.clone()).size(13),
|
||||
Space::new().width(Fill),
|
||||
install_controls(repo, tags, selected, true),
|
||||
]
|
||||
|
|
@ -318,7 +276,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
|||
}
|
||||
|
||||
if !m.status.is_empty() {
|
||||
col = col.push(text(m.status.to_string()).size(11).color(DIM));
|
||||
col = col.push(text(m.status.to_string()).size(11).style(muted_style));
|
||||
}
|
||||
col.into()
|
||||
}
|
||||
|
|
@ -329,17 +287,17 @@ pub fn view_window<'a>(
|
|||
loaded: &FxHashSet<String>,
|
||||
market: MarketView,
|
||||
) -> Element<'a, Message> {
|
||||
let title = text("Plugins").size(20).color(WHITE);
|
||||
let title = text("Plugins").size(20);
|
||||
let subtitle = text("Add-ons load from the plugins folder. Install from a repository below.")
|
||||
.size(12)
|
||||
.color(DIM);
|
||||
.style(muted_style);
|
||||
|
||||
let mut list = column![].spacing(10);
|
||||
// Installed external packages (from the plugins folder).
|
||||
if externals.is_empty() {
|
||||
list = list.push(text("No plugins installed yet.").size(13).color(DIM));
|
||||
list = list.push(text("No plugins installed yet.").size(13).style(muted_style));
|
||||
} else {
|
||||
list = list.push(text("Installed").size(13).color(ACCENT));
|
||||
list = list.push(text("Installed").size(13).style(primary_style));
|
||||
for p in externals {
|
||||
list = list.push(external_card(
|
||||
p,
|
||||
|
|
@ -360,8 +318,10 @@ pub fn view_window<'a>(
|
|||
.width(Fill)
|
||||
.height(Fill),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::app::Message;
|
||||
use iced::widget::{column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Element, Fill, Theme};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Display name of the primary accelerator modifier on this platform.
|
||||
|
|
@ -14,49 +14,26 @@ const MOD: &str = "Cmd";
|
|||
#[cfg(not(target_os = "macos"))]
|
||||
const MOD: &str = "Ctrl";
|
||||
|
||||
const TB: Color = Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.13,
|
||||
a: 1.0,
|
||||
};
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const KEY: Color = Color {
|
||||
r: 0.40,
|
||||
g: 0.70,
|
||||
b: 1.00,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
fn hdivider<'a>() -> Element<'a, Message> {
|
||||
container(Space::new().width(Fill).height(1))
|
||||
.width(Fill)
|
||||
.height(1)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BORDER)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.neutral.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
|
|
@ -69,7 +46,7 @@ fn shortcut_row<'a>(
|
|||
row![
|
||||
text(key.into())
|
||||
.size(11)
|
||||
.color(KEY)
|
||||
.style(primary_style)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.width(160),
|
||||
text(action).size(11),
|
||||
|
|
@ -81,7 +58,7 @@ fn shortcut_row<'a>(
|
|||
}
|
||||
|
||||
fn section<'a>(title: impl Into<Cow<'static, str>>) -> Element<'a, Message> {
|
||||
container(text(title.into()).size(11).color(DIM))
|
||||
container(text(title.into()).size(11).style(muted_style))
|
||||
.padding(iced::Padding {
|
||||
top: 6.0,
|
||||
right: 0.0,
|
||||
|
|
@ -99,12 +76,14 @@ pub fn view_window<'a>(
|
|||
row![
|
||||
text("Type SHORTCUTS SET <key> <cmd> to add custom shortcuts.")
|
||||
.size(10)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(TB)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.weakest.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
@ -147,7 +126,7 @@ pub fn view_window<'a>(
|
|||
rows.push(
|
||||
text(" (none — use: SHORTCUTS SET <key> <command>)")
|
||||
.size(11)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -158,7 +137,7 @@ pub fn view_window<'a>(
|
|||
row![
|
||||
text(key.as_str())
|
||||
.size(11)
|
||||
.color(KEY)
|
||||
.style(primary_style)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.width(160),
|
||||
text(cmd.as_str()).size(11),
|
||||
|
|
@ -179,26 +158,25 @@ pub fn view_window<'a>(
|
|||
// ── Header row with accent ────────────────────────────────────────────
|
||||
let header = container(
|
||||
row![
|
||||
text("Key").size(10).color(ACCENT).width(160),
|
||||
text("Action").size(10).color(ACCENT),
|
||||
text("Key").size(10).style(primary_style).width(160),
|
||||
text("Action").size(10).style(primary_style),
|
||||
]
|
||||
.spacing(8)
|
||||
.padding([4, 16]),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.13,
|
||||
g: 0.13,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
})),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().primary.weak.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill);
|
||||
|
||||
container(column![toolbar, hdivider(), header, hdivider(), content].spacing(0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -1,60 +1,33 @@
|
|||
use crate::app::Message;
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
||||
use iced::{Background, Border, Element, Fill, Theme};
|
||||
|
||||
const BG: Color = Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 1.0,
|
||||
};
|
||||
const PANEL: Color = Color {
|
||||
r: 0.12,
|
||||
g: 0.12,
|
||||
b: 0.12,
|
||||
a: 1.0,
|
||||
};
|
||||
const BORDER: Color = Color {
|
||||
r: 0.30,
|
||||
g: 0.30,
|
||||
b: 0.30,
|
||||
a: 1.0,
|
||||
};
|
||||
const DIM: Color = Color {
|
||||
r: 0.55,
|
||||
g: 0.55,
|
||||
b: 0.55,
|
||||
a: 1.0,
|
||||
};
|
||||
const ACCENT: Color = Color {
|
||||
r: 0.25,
|
||||
g: 0.50,
|
||||
b: 0.85,
|
||||
a: 1.0,
|
||||
};
|
||||
const WHITE: Color = Color {
|
||||
r: 0.92,
|
||||
g: 0.92,
|
||||
b: 0.92,
|
||||
a: 1.0,
|
||||
};
|
||||
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||
}
|
||||
}
|
||||
|
||||
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||
iced::widget::text::Style {
|
||||
color: Some(theme.extended_palette().primary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders one of the two "Installed" / "Latest" cards. The `highlight`
|
||||
/// flag tints the border + label with the accent colour, making the new
|
||||
/// version the visual anchor of the row.
|
||||
fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Element<'a, Message> {
|
||||
let label_color = if highlight { ACCENT } else { DIM };
|
||||
let value_color = WHITE;
|
||||
let border_color = if highlight { ACCENT } else { BORDER };
|
||||
let bg = if highlight {
|
||||
Color { r: 0.10, g: 0.16, b: 0.22, a: 1.0 } // accent-tinted dark
|
||||
} else {
|
||||
PANEL
|
||||
};
|
||||
container(
|
||||
column![
|
||||
text(label).size(10).color(label_color),
|
||||
text(value).size(20).color(value_color),
|
||||
text(label)
|
||||
.size(10)
|
||||
.style(move |theme: &Theme| if highlight {
|
||||
primary_style(theme)
|
||||
} else {
|
||||
muted_style(theme)
|
||||
}),
|
||||
text(value).size(20),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
|
|
@ -67,14 +40,26 @@ fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Elem
|
|||
left: 12.0,
|
||||
})
|
||||
.align_x(iced::Center)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
.style(move |theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
let pair = if highlight {
|
||||
palette.primary.weak
|
||||
} else {
|
||||
palette.background.base
|
||||
};
|
||||
container::Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
border: Border {
|
||||
color: border_color,
|
||||
color: if highlight {
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.neutral.color
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
@ -98,24 +83,23 @@ fn render_notes_line<'a>(raw: &str) -> Element<'a, Message> {
|
|||
if let Some(rest) = trimmed.strip_prefix("## ") {
|
||||
return text(strip_inline_md(rest))
|
||||
.size(13)
|
||||
.color(ACCENT)
|
||||
.style(primary_style)
|
||||
.into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("### ") {
|
||||
return text(strip_inline_md(rest))
|
||||
.size(12)
|
||||
.color(WHITE)
|
||||
.into();
|
||||
}
|
||||
if let Some(rest) = trimmed.strip_prefix("- ").or_else(|| trimmed.strip_prefix("* ")) {
|
||||
return row![
|
||||
container(crate::ui::icons::tinted(crate::ui::icons::DOT, 5.0, DIM)).width(14),
|
||||
text(strip_inline_md(rest)).size(11).color(WHITE),
|
||||
container(crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 5.0)).width(14),
|
||||
text(strip_inline_md(rest)).size(11),
|
||||
]
|
||||
.spacing(4)
|
||||
.into();
|
||||
}
|
||||
text(strip_inline_md(trimmed)).size(11).color(WHITE).into()
|
||||
text(strip_inline_md(trimmed)).size(11).into()
|
||||
}
|
||||
|
||||
/// Drop `**…**` and `` `…` `` markers without preserving emphasis (iced 0.14
|
||||
|
|
@ -139,10 +123,10 @@ fn strip_inline_md(s: &str) -> String {
|
|||
pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||
let header = container(
|
||||
column![
|
||||
text("New Release Available").size(20).color(ACCENT),
|
||||
text("New Release Available").size(20).style(primary_style),
|
||||
text("A newer Open CAD Studio version is published on GitHub.")
|
||||
.size(11)
|
||||
.color(DIM),
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
|
|
@ -165,10 +149,9 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
|||
false,
|
||||
);
|
||||
let latest_card = version_card("Latest", format!("v{}", latest), true);
|
||||
let arrow = container(crate::ui::icons::tinted(
|
||||
let arrow = container(crate::ui::icons::themed_secondary(
|
||||
crate::ui::icons::ARROW_LONG_RIGHT,
|
||||
20.0,
|
||||
DIM,
|
||||
))
|
||||
.width(iced::Length::Fixed(32.0))
|
||||
.height(Fill)
|
||||
|
|
@ -181,50 +164,12 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
|||
|
||||
let later_btn = button(text("Later").size(11))
|
||||
.on_press(Message::UpdateNoticeClose)
|
||||
.style(|_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match st {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.22,
|
||||
g: 0.22,
|
||||
b: 0.22,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.18,
|
||||
g: 0.18,
|
||||
b: 0.18,
|
||||
a: 1.0,
|
||||
},
|
||||
})),
|
||||
text_color: WHITE,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::secondary)
|
||||
.padding([6, 16]);
|
||||
|
||||
let open_btn = button(text("Open Release Page").size(11))
|
||||
.on_press(Message::UpdateNoticeOpenRelease)
|
||||
.style(|_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match st {
|
||||
button::Status::Hovered | button::Status::Pressed => Color {
|
||||
r: 0.20,
|
||||
g: 0.42,
|
||||
b: 0.72,
|
||||
a: 1.0,
|
||||
},
|
||||
_ => ACCENT,
|
||||
})),
|
||||
text_color: WHITE,
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.style(button::primary)
|
||||
.padding([6, 16]);
|
||||
|
||||
let footer = row![Space::new().width(Fill), later_btn, open_btn]
|
||||
|
|
@ -240,7 +185,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
|||
// Release notes panel. Rendered as a light-markdown column inside a
|
||||
// bordered scrollable so long bodies stay contained and don't
|
||||
// explode the window. Empty body → "No release notes provided."
|
||||
let notes_heading = container(text("What's new").size(11).color(DIM))
|
||||
let notes_heading = container(text("What's new").size(11).style(muted_style))
|
||||
.padding(iced::Padding {
|
||||
top: 10.0,
|
||||
right: 0.0,
|
||||
|
|
@ -251,7 +196,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
|||
let notes_body: Element<'a, Message> = if body.trim().is_empty() {
|
||||
text("No release notes provided.")
|
||||
.size(11)
|
||||
.color(DIM)
|
||||
.style(muted_style)
|
||||
.into()
|
||||
} else {
|
||||
let mut col = column![].spacing(4);
|
||||
|
|
@ -264,15 +209,7 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
|||
let notes_block = container(notes_body)
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
.style(container::bordered_box);
|
||||
|
||||
// Wrap notes_block in a Fill-height container outside the column so it
|
||||
// greedily claims every pixel left over after the fixed-height rows
|
||||
|
|
@ -294,8 +231,10 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
|||
left: 20.0,
|
||||
}),
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.extended_palette().background.base.color,
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
.width(Fill)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use iced::advanced::layout::{self, Layout};
|
|||
use iced::advanced::widget::{self, tree, Widget};
|
||||
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
||||
use iced::{
|
||||
Background, Border, Color, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
|
||||
Background, Border, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
|
||||
Theme, Vector,
|
||||
};
|
||||
|
||||
|
|
@ -1360,12 +1360,7 @@ impl<'a> Widget<Message, Theme, Renderer> for ReorderTab<'a> {
|
|||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
Background::Color(Color {
|
||||
r: 0.20,
|
||||
g: 0.55,
|
||||
b: 0.90,
|
||||
a: 1.0,
|
||||
}),
|
||||
Background::Color(theme.extended_palette().primary.base.color),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue