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 {
|
pub struct AppConfig {
|
||||||
/// Input modes, backup, plugin lists, viewport background colours, …
|
/// Input modes, backup, plugin lists, viewport background colours, …
|
||||||
pub settings: UserSettings,
|
pub settings: UserSettings,
|
||||||
|
/// Iced theme selection and the six base colours used by a custom theme.
|
||||||
|
pub theme: UiThemeConfig,
|
||||||
/// Recent-files list + retained count.
|
/// Recent-files list + retained count.
|
||||||
pub recent: RecentConfig,
|
pub recent: RecentConfig,
|
||||||
/// Last selected section on the tabbed Start page.
|
/// Last selected section on the tabbed Start page.
|
||||||
|
|
@ -35,6 +37,7 @@ impl Default for AppConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
settings: UserSettings::default(),
|
settings: UserSettings::default(),
|
||||||
|
theme: UiThemeConfig::default(),
|
||||||
recent: RecentConfig::default(),
|
recent: RecentConfig::default(),
|
||||||
start: StartConfig::default(),
|
start: StartConfig::default(),
|
||||||
statusbar: StatusBarConfig::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)]
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct RecentConfig {
|
pub struct RecentConfig {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
mod alias;
|
mod alias;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
mod automation;
|
mod automation;
|
||||||
mod config;
|
pub(crate) mod config;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use automation::{export_headless, serve};
|
pub use automation::{export_headless, serve};
|
||||||
mod command_driver;
|
mod command_driver;
|
||||||
|
|
@ -688,6 +688,8 @@ pub(super) struct OpenCADStudio {
|
||||||
|
|
||||||
// ── Color Scheme ──────────────────────────────────────────────────────
|
// ── Color Scheme ──────────────────────────────────────────────────────
|
||||||
active_theme: Theme,
|
active_theme: Theme,
|
||||||
|
ui_theme: config::UiThemeConfig,
|
||||||
|
theme_color_inputs: [String; 6],
|
||||||
|
|
||||||
// ── Keyboard Shortcut Editor ──────────────────────────────────────────
|
// ── Keyboard Shortcut Editor ──────────────────────────────────────────
|
||||||
/// User-defined function-key overrides: "F3" → command string.
|
/// User-defined function-key overrides: "F3" → command string.
|
||||||
|
|
@ -1474,6 +1476,10 @@ pub enum Message {
|
||||||
OptionsOpen,
|
OptionsOpen,
|
||||||
/// Set the default type/version used when first saving a new drawing.
|
/// Set the default type/version used when first saving a new drawing.
|
||||||
DefaultSaveFormatChanged(String),
|
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,
|
ClearScene,
|
||||||
SetWireframe(bool),
|
SetWireframe(bool),
|
||||||
/// Set the active tab's render mode (one of acadrust's seven visual
|
/// Set the active tab's render mode (one of acadrust's seven visual
|
||||||
|
|
@ -2626,6 +2632,8 @@ impl OpenCADStudio {
|
||||||
active_plot_style: None,
|
active_plot_style: None,
|
||||||
// Color scheme (default: dark CAD-style)
|
// Color scheme (default: dark CAD-style)
|
||||||
active_theme: Theme::Dark,
|
active_theme: Theme::Dark,
|
||||||
|
ui_theme: config::UiThemeConfig::default(),
|
||||||
|
theme_color_inputs: config::UiThemePalette::default().hex_values(),
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
shortcut_overrides: rustc_hash::FxHashMap::default(),
|
shortcut_overrides: rustc_hash::FxHashMap::default(),
|
||||||
// Command aliases (populated from ocad.pgp just after construction)
|
// 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 {
|
pub(in crate::app) fn current_config(&self) -> crate::app::config::AppConfig {
|
||||||
crate::app::config::AppConfig {
|
crate::app::config::AppConfig {
|
||||||
settings: self.current_settings(),
|
settings: self.current_settings(),
|
||||||
|
theme: self.ui_theme.clone(),
|
||||||
recent: crate::app::config::RecentConfig {
|
recent: crate::app::config::RecentConfig {
|
||||||
files: self
|
files: self
|
||||||
.recent_files
|
.recent_files
|
||||||
|
|
@ -342,6 +343,9 @@ impl OpenCADStudio {
|
||||||
/// Distribute a loaded config into live app state (called once at startup).
|
/// 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) {
|
pub(in crate::app) fn apply_config(&mut self, cfg: crate::app::config::AppConfig) {
|
||||||
self.apply_settings(&cfg.settings);
|
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
|
self.recent_files = cfg
|
||||||
.recent
|
.recent
|
||||||
.files
|
.files
|
||||||
|
|
|
||||||
|
|
@ -3551,7 +3551,12 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::SetTheme(theme) => {
|
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.active_theme = theme;
|
||||||
|
self.persist_settings_if_changed();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3620,6 +3625,37 @@ impl OpenCADStudio {
|
||||||
Message::DefaultSaveFormatChanged(format) => {
|
Message::DefaultSaveFormatChanged(format) => {
|
||||||
self.default_save_format =
|
self.default_save_format =
|
||||||
crate::io::canonical_save_format(&format).to_string();
|
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()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use super::super::Message;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, container, mouse_area, row,
|
button, container, mouse_area, row,
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Theme};
|
use iced::{Background, Border, Element, Theme};
|
||||||
|
|
||||||
pub(super) fn viewport_controls<'a>(
|
pub(super) fn viewport_controls<'a>(
|
||||||
render_mode: acadrust::entities::ViewportRenderMode,
|
render_mode: acadrust::entities::ViewportRenderMode,
|
||||||
|
|
@ -23,64 +23,58 @@ pub(super) fn viewport_controls<'a>(
|
||||||
RenderModeChoice(M::FlatShadedWithEdges),
|
RenderModeChoice(M::FlatShadedWithEdges),
|
||||||
RenderModeChoice(M::GouraudShadedWithEdges),
|
RenderModeChoice(M::GouraudShadedWithEdges),
|
||||||
];
|
];
|
||||||
let light = Color { r: 0.85, g: 0.85, b: 0.85, a: 1.0 };
|
let danger_btn = move |bytes: &'static [u8], msg: Message| {
|
||||||
let accent = Color { r: 0.45, g: 0.70, b: 1.0, a: 1.0 };
|
button(crate::ui::icons::themed_danger(bytes, 15.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))
|
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
.style(move |theme: &Theme, status| iced::widget::button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: matches!(
|
||||||
|
status,
|
||||||
iced::widget::button::Status::Hovered
|
iced::widget::button::Status::Hovered
|
||||||
| iced::widget::button::Status::Pressed => Color {
|
| iced::widget::button::Status::Pressed
|
||||||
r: 0.25,
|
)
|
||||||
g: 0.25,
|
.then_some(Background::Color(
|
||||||
b: 0.25,
|
theme.extended_palette().danger.weak.color
|
||||||
a: 0.9,
|
)),
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text_color: color,
|
text_color: theme.extended_palette().danger.base.color,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Borderless icon button; an `active` toggle gets an accent tint + fill.
|
// Borderless icon button; an `active` toggle gets an accent tint + fill.
|
||||||
let icon_btn = move |bytes: &'static [u8], active: bool, msg: Message| {
|
let icon_btn = move |bytes: &'static [u8], active: bool, msg: Message| {
|
||||||
let tint = if active { accent } else { light };
|
let icon = if active {
|
||||||
button(crate::ui::icons::tinted(bytes, 15.0, tint))
|
crate::ui::icons::themed_primary(bytes, 15.0)
|
||||||
|
} else {
|
||||||
|
crate::ui::icons::themed(bytes, 15.0)
|
||||||
|
};
|
||||||
|
button(icon)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (active, status) {
|
let palette = theme.extended_palette();
|
||||||
(_, iced::widget::button::Status::Hovered) => Color {
|
let pair = match (active, status) {
|
||||||
r: 0.25,
|
(_, iced::widget::button::Status::Hovered) => {
|
||||||
g: 0.25,
|
Some(palette.background.strong)
|
||||||
b: 0.25,
|
}
|
||||||
a: 0.9,
|
(true, _) => Some(palette.primary.weak),
|
||||||
},
|
(false, _) => None,
|
||||||
(true, _) => Color {
|
};
|
||||||
r: 0.16,
|
iced::widget::button::Style {
|
||||||
g: 0.22,
|
background: pair.map(|p| Background::Color(p.color)),
|
||||||
b: 0.32,
|
|
||||||
a: 0.9,
|
|
||||||
},
|
|
||||||
(false, _) => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text_color: tint,
|
text_color: pair
|
||||||
|
.map(|p| p.text)
|
||||||
|
.unwrap_or(palette.background.base.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -92,27 +86,27 @@ pub(super) fn viewport_controls<'a>(
|
||||||
)
|
)
|
||||||
.text_size(11)
|
.text_size(11)
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.style(move |_: &Theme, _| iced::widget::pick_list::Style {
|
.style(move |theme: &Theme, _| {
|
||||||
background: Background::Color(Color::TRANSPARENT),
|
let text = theme.extended_palette().background.base.text;
|
||||||
|
iced::widget::pick_list::Style {
|
||||||
|
background: Background::Color(iced::Color::TRANSPARENT),
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text_color: light,
|
text_color: text,
|
||||||
placeholder_color: light,
|
placeholder_color: text.scale_alpha(0.68),
|
||||||
handle_color: light,
|
handle_color: text,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Thin vertical divider between control groups.
|
// Thin vertical divider between control groups.
|
||||||
let sep = || {
|
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 {
|
iced::widget::container::Style {
|
||||||
background: Some(Background::Color(Color {
|
background: Some(Background::Color(
|
||||||
r: 0.45,
|
theme.extended_palette().background.neutral.color.scale_alpha(0.7)
|
||||||
g: 0.45,
|
)),
|
||||||
b: 0.45,
|
|
||||||
a: 0.7,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -139,7 +133,7 @@ pub(super) fn viewport_controls<'a>(
|
||||||
// would only fire on release). Placed just left of Close.
|
// would only fire on release). Placed just left of Close.
|
||||||
if tile_count > 1 {
|
if tile_count > 1 {
|
||||||
let drag = mouse_area(
|
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])
|
.padding([4, 6])
|
||||||
.style(|_: &Theme| iced::widget::container::Style {
|
.style(|_: &Theme| iced::widget::container::Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
|
|
@ -155,30 +149,25 @@ pub(super) fn viewport_controls<'a>(
|
||||||
.push(sep())
|
.push(sep())
|
||||||
.push(drag)
|
.push(drag)
|
||||||
.push(sep())
|
.push(sep())
|
||||||
.push(tinted_btn(crate::ui::icons::CLOSE, red, Message::CloseModelViewport));
|
.push(danger_btn(crate::ui::icons::CLOSE, Message::CloseModelViewport));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container(bar)
|
container(bar)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.style(|_: &Theme| iced::widget::container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(Color {
|
let palette = theme.extended_palette();
|
||||||
r: 0.10,
|
iced::widget::container::Style {
|
||||||
g: 0.10,
|
background: Some(Background::Color(
|
||||||
b: 0.10,
|
palette.background.weak.color.scale_alpha(0.92)
|
||||||
a: 0.75,
|
)),
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: Color {
|
color: palette.background.neutral.color,
|
||||||
r: 0.35,
|
|
||||||
g: 0.35,
|
|
||||||
b: 0.35,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
use super::super::{Message, OpenCADStudio};
|
use super::super::{Message, OpenCADStudio};
|
||||||
use iced::widget::{button, column, container, pick_list, row, text, Space};
|
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 {
|
impl OpenCADStudio {
|
||||||
/// Title shown in the active modal's title bar, left of the move/close
|
/// 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)
|
sized(crate::ui::window::alias_editor::view_window(&self.alias_editor_rows), 480, 520)
|
||||||
}
|
}
|
||||||
super::super::ModalKind::Options => sized(
|
super::super::ModalKind::Options => sized(
|
||||||
crate::ui::window::options::view_window(&self.default_save_format),
|
crate::ui::window::options::view_window(
|
||||||
480,
|
&self.default_save_format,
|
||||||
190,
|
&self.ui_theme,
|
||||||
|
&self.theme_color_inputs,
|
||||||
|
),
|
||||||
|
520,
|
||||||
|
500,
|
||||||
),
|
),
|
||||||
super::super::ModalKind::PluginManager => sized(
|
super::super::ModalKind::PluginManager => sized(
|
||||||
crate::ui::window::plugin_manager::view_window(
|
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
|
/// Compact Save-As options dialog: pick the format/version and a default file
|
||||||
/// name. The destination folder and overwrite confirmation come from the
|
/// name. The destination folder and overwrite confirmation come from the
|
||||||
/// native OS save dialog (native) or the browser download (web) that follows.
|
/// 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> {
|
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
|
let sel_fmt = crate::io::SAVE_FORMAT_OPTIONS
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.find(|&s| s == format);
|
.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();
|
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());
|
items.push(Space::new().height(12).into());
|
||||||
|
|
||||||
// Web has no native file dialog, so the file name is typed here. On native
|
// 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.
|
// the OS save dialog collects the name, so this field is omitted.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[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(
|
items.push(
|
||||||
row![
|
row![
|
||||||
label("File name:").width(70),
|
label("File name:").width(70),
|
||||||
iced::widget::text_input("drawing.dwg", filename)
|
iced::widget::text_input("drawing.dwg", filename)
|
||||||
.on_input(Message::SaveDialogFilenameChanged)
|
.on_input(Message::SaveDialogFilenameChanged)
|
||||||
.style(input_sty)
|
|
||||||
.size(13)
|
.size(13)
|
||||||
.padding([5, 8])
|
.padding([5, 8])
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
|
|
@ -860,9 +795,9 @@ fn save_as_dialog_window<'a>(filename: &'a str, format: &'a str) -> Element<'a,
|
||||||
items.push(
|
items.push(
|
||||||
row![
|
row![
|
||||||
Space::new().width(Fill),
|
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),
|
Space::new().width(8),
|
||||||
btn("Cancel", Message::SaveDialogCancel, BTN_GREY, BTN_GHOV),
|
dialog_button("Cancel", Message::SaveDialogCancel, button::secondary),
|
||||||
]
|
]
|
||||||
.into(),
|
.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);
|
let body = column(items).spacing(0);
|
||||||
|
|
||||||
container(body)
|
container(body)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([14, 16])
|
.padding([14, 16])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(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> {
|
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 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(
|
container(
|
||||||
column![
|
column![
|
||||||
text(body_text).size(13).color(TEXT_COL),
|
text(body_text).size(13),
|
||||||
iced::widget::Space::new().height(20),
|
iced::widget::Space::new().height(20),
|
||||||
row![
|
row![
|
||||||
btn("Save", Message::UnsavedDialogSave, BTN_SAVE, BTN_HOVER),
|
dialog_button("Save", Message::UnsavedDialogSave, button::primary),
|
||||||
iced::widget::Space::new().width(8),
|
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),
|
iced::widget::Space::new().width(8),
|
||||||
btn("Cancel", Message::UnsavedDialogCancel, BTN_DISC, BTN_DHOV),
|
dialog_button("Cancel", Message::UnsavedDialogCancel, button::secondary),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
.spacing(0),
|
.spacing(0),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.center(Fill)
|
.center(Fill)
|
||||||
.padding([24, 28])
|
.padding([24, 28])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -971,55 +837,6 @@ fn unsaved_changes_dialog_window(name: &str) -> Element<'static, Message> {
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn file_in_use_dialog_window(path: &str, error: &str) -> Element<'static, Message> {
|
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)
|
let file_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|name| name.to_string_lossy().into_owned())
|
.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 heading = format!("\"{file_name}\" could not be saved.");
|
||||||
let path_line = format!("Path: {path}");
|
let path_line = format!("Path: {path}");
|
||||||
let details = format!("Details: {error}");
|
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(
|
container(
|
||||||
column![
|
column![
|
||||||
text(heading).size(14).color(TEXT_COL),
|
text(heading).size(14),
|
||||||
Space::new().height(8),
|
Space::new().height(8),
|
||||||
text(
|
text(
|
||||||
"The file is open or being used by another application. \
|
"The file is open or being used by another application. \
|
||||||
Close it there and retry, or save this drawing under a different name."
|
Close it there and retry, or save this drawing under a different name."
|
||||||
)
|
)
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(TEXT_COL)
|
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
Space::new().height(12),
|
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),
|
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),
|
Space::new().height(18),
|
||||||
row![
|
row![
|
||||||
btn(
|
dialog_button(
|
||||||
"Retry",
|
"Retry",
|
||||||
Message::SaveFileInUseRetry,
|
Message::SaveFileInUseRetry,
|
||||||
BTN_PRIMARY,
|
button::primary
|
||||||
BTN_PRIMARY_HOVER
|
|
||||||
),
|
),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
btn(
|
dialog_button(
|
||||||
"Save As",
|
"Save As",
|
||||||
Message::SaveFileInUseSaveAs,
|
Message::SaveFileInUseSaveAs,
|
||||||
BTN_SECONDARY,
|
button::secondary
|
||||||
BTN_SECONDARY_HOVER
|
|
||||||
),
|
),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
btn(
|
dialog_button(
|
||||||
"Cancel",
|
"Cancel",
|
||||||
Message::SaveFileInUseCancel,
|
Message::SaveFileInUseCancel,
|
||||||
BTN_SECONDARY,
|
button::secondary
|
||||||
BTN_SECONDARY_HOVER
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
.spacing(0),
|
.spacing(0),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([18, 20])
|
.padding([18, 20])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn external_change_dialog_window(path: &str) -> Element<'static, Message> {
|
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)
|
let file_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|name| name.to_string_lossy().into_owned())
|
.map(|name| name.to_string_lossy().into_owned())
|
||||||
.unwrap_or_else(|| "Drawing".to_string());
|
.unwrap_or_else(|| "Drawing".to_string());
|
||||||
let heading = format!("\"{file_name}\" was changed by another application.");
|
let heading = format!("\"{file_name}\" was changed by another application.");
|
||||||
let path_line = format!("Path: {path}");
|
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(
|
container(
|
||||||
column![
|
column![
|
||||||
text(heading).size(14).color(TEXT_COL),
|
text(heading).size(14),
|
||||||
Space::new().height(8),
|
Space::new().height(8),
|
||||||
text(
|
text(
|
||||||
"Saving now could destroy those external changes. Reload the disk copy, \
|
"Saving now could destroy those external changes. Reload the disk copy, \
|
||||||
save your local work elsewhere, or explicitly overwrite it."
|
save your local work elsewhere, or explicitly overwrite it."
|
||||||
)
|
)
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(TEXT_COL)
|
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
Space::new().height(12),
|
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),
|
Space::new().height(18),
|
||||||
row![
|
row![
|
||||||
btn(
|
dialog_button(
|
||||||
"Reload from Disk",
|
"Reload from Disk",
|
||||||
Message::ExternalChangeReload,
|
Message::ExternalChangeReload,
|
||||||
BTN_PRIMARY,
|
button::primary
|
||||||
BTN_PRIMARY_HOVER
|
|
||||||
),
|
),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
btn(
|
dialog_button(
|
||||||
"Save As",
|
"Save As",
|
||||||
Message::ExternalChangeSaveAs,
|
Message::ExternalChangeSaveAs,
|
||||||
BTN_SECONDARY,
|
button::secondary
|
||||||
BTN_SECONDARY_HOVER
|
|
||||||
),
|
),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
btn(
|
dialog_button(
|
||||||
"Overwrite",
|
"Overwrite",
|
||||||
Message::ExternalChangeOverwrite,
|
Message::ExternalChangeOverwrite,
|
||||||
BTN_DANGER,
|
button::danger
|
||||||
BTN_DANGER_HOVER
|
|
||||||
),
|
),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
btn(
|
dialog_button(
|
||||||
"Cancel",
|
"Cancel",
|
||||||
Message::ExternalChangeCancel,
|
Message::ExternalChangeCancel,
|
||||||
BTN_SECONDARY,
|
button::secondary
|
||||||
BTN_SECONDARY_HOVER
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
.spacing(0),
|
.spacing(0),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([18, 20])
|
.padding([18, 20])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(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
|
/// 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).
|
/// 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> {
|
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!(
|
let body_text = format!(
|
||||||
"This drawing contains {count} AEC/Civil objects that \"{target}\" \
|
"This drawing contains {count} AEC/Civil objects that \"{target}\" \
|
||||||
cannot store, so they will not be saved.\n\n\
|
cannot store, so they will not be saved.\n\n\
|
||||||
To keep them, save in the source version ({src_version})."
|
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(
|
container(
|
||||||
column![
|
column![
|
||||||
text(body_text).size(13).color(TEXT_COL),
|
text(body_text).size(13),
|
||||||
iced::widget::Space::new().height(20),
|
iced::widget::Space::new().height(20),
|
||||||
row![
|
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),
|
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),
|
iced::widget::Space::new().width(8),
|
||||||
btn("Back", Message::AecDropBack, BTN_DISC, BTN_DHOV),
|
dialog_button("Back", Message::AecDropBack, button::secondary),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
.spacing(0),
|
.spacing(0),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.center(Fill)
|
.center(Fill)
|
||||||
.padding([24, 28])
|
.padding([24, 28])
|
||||||
.into()
|
.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"
|
/// Confirm deleting layer(s) that still have objects on them. "Delete Objects"
|
||||||
/// erases them and removes the layers; "Cancel" leaves everything.
|
/// erases them and removes the layers; "Cancel" leaves everything.
|
||||||
fn layer_delete_warning_window(names: &[String], count: usize) -> Element<'static, Message> {
|
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 obj = if count == 1 { "object" } else { "objects" };
|
||||||
let subject = if names.len() == 1 {
|
let subject = if names.len() == 1 {
|
||||||
format!("Layer \"{}\"", names[0])
|
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" }
|
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(
|
container(
|
||||||
column![
|
column![
|
||||||
text(body_text).size(13).color(TEXT_COL),
|
text(body_text).size(13),
|
||||||
iced::widget::Space::new().height(20),
|
iced::widget::Space::new().height(20),
|
||||||
row![
|
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),
|
iced::widget::Space::new().width(8),
|
||||||
btn("Cancel", Message::CloseModal, BTN_CANCEL, BTN_CANCEL_HOV),
|
dialog_button("Cancel", Message::CloseModal, button::secondary),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
.spacing(0),
|
.spacing(0),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.center(Fill)
|
.center(Fill)
|
||||||
.padding([24, 28])
|
.padding([24, 28])
|
||||||
.into()
|
.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`
|
/// just dismisses. Either answer flips the persisted `default_assoc_prompted`
|
||||||
/// flag so the dialog never reappears.
|
/// flag so the dialog never reappears.
|
||||||
fn default_assoc_dialog_window() -> Element<'static, Message> {
|
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(
|
container(
|
||||||
column![
|
column![
|
||||||
text("Make Open CAD Studio your default CAD app?")
|
text("Make Open CAD Studio your default CAD app?")
|
||||||
.size(15)
|
.size(15),
|
||||||
.color(TEXT_COL),
|
|
||||||
iced::widget::Space::new().height(10),
|
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.")
|
text("Open .dwg and .dxf drawings in Open CAD Studio by default. You can change this later in your system settings.")
|
||||||
.size(12)
|
.size(12)
|
||||||
.color(DIM_COL),
|
.style(dialog_muted_text_style),
|
||||||
iced::widget::Space::new().height(22),
|
iced::widget::Space::new().height(22),
|
||||||
row![
|
row![
|
||||||
iced::widget::Space::new().width(Fill),
|
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),
|
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),
|
.align_y(iced::Center),
|
||||||
]
|
]
|
||||||
.spacing(0),
|
.spacing(0),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(dialog_body_style)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.center(Fill)
|
.center(Fill)
|
||||||
.padding([24, 28])
|
.padding([24, 28])
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -26,19 +26,6 @@ pub(super) fn text_inline_overlay(
|
||||||
ed: &super::super::text_inline::TextInlineState,
|
ed: &super::super::text_inline::TextInlineState,
|
||||||
canvas: (f32, f32),
|
canvas: (f32, f32),
|
||||||
) -> Element<'_, Message> {
|
) -> 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)
|
let field = text_input("Text", &ed.value)
|
||||||
.id(iced::widget::Id::new(TEXT_INLINE_ID))
|
.id(iced::widget::Id::new(TEXT_INLINE_ID))
|
||||||
.on_input(Message::TextInlineInput)
|
.on_input(Message::TextInlineInput)
|
||||||
|
|
@ -48,14 +35,17 @@ pub(super) fn text_inline_overlay(
|
||||||
.width(iced::Length::Fixed(240.0));
|
.width(iced::Length::Fixed(240.0));
|
||||||
|
|
||||||
let panel = container(field)
|
let panel = container(field)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 5.0.into(),
|
radius: 5.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(4);
|
.padding(4);
|
||||||
|
|
||||||
|
|
@ -195,7 +185,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
||||||
&self,
|
&self,
|
||||||
_state: &MTextPreviewState,
|
_state: &MTextPreviewState,
|
||||||
renderer: &iced::Renderer,
|
renderer: &iced::Renderer,
|
||||||
_theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: iced::Rectangle,
|
bounds: iced::Rectangle,
|
||||||
_cursor: iced::mouse::Cursor,
|
_cursor: iced::mouse::Cursor,
|
||||||
) -> Vec<iced::widget::canvas::Geometry> {
|
) -> Vec<iced::widget::canvas::Geometry> {
|
||||||
|
|
@ -221,12 +211,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
||||||
);
|
);
|
||||||
frame.fill(
|
frame.fill(
|
||||||
&rect,
|
&rect,
|
||||||
Color {
|
theme.extended_palette().primary.base.color.scale_alpha(0.45),
|
||||||
r: 0.20,
|
|
||||||
g: 0.42,
|
|
||||||
b: 0.72,
|
|
||||||
a: 0.45,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,12 +247,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&path,
|
&path,
|
||||||
Stroke::default()
|
Stroke::default()
|
||||||
.with_color(Color {
|
.with_color(theme.extended_palette().warning.base.color)
|
||||||
r: 0.95,
|
|
||||||
g: 0.95,
|
|
||||||
b: 0.55,
|
|
||||||
a: 1.0,
|
|
||||||
})
|
|
||||||
.with_width(1.5),
|
.with_width(1.5),
|
||||||
);
|
);
|
||||||
} else if collapsed {
|
} else if collapsed {
|
||||||
|
|
@ -291,12 +271,7 @@ impl iced::widget::canvas::Program<Message> for MTextPreview {
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&path,
|
&path,
|
||||||
Stroke::default()
|
Stroke::default()
|
||||||
.with_color(Color {
|
.with_color(theme.extended_palette().warning.base.color)
|
||||||
r: 0.95,
|
|
||||||
g: 0.95,
|
|
||||||
b: 0.55,
|
|
||||||
a: 1.0,
|
|
||||||
})
|
|
||||||
.with_width(1.5),
|
.with_width(1.5),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -344,59 +319,34 @@ pub(super) fn mtext_editor_overlay<'a>(
|
||||||
modal_resize: iced::Vector,
|
modal_resize: iced::Vector,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
use super::super::mtext_editor::{JustifyChoice, MTextFmt, ParaAlign};
|
use super::super::mtext_editor::{JustifyChoice, MTextFmt, ParaAlign};
|
||||||
use iced::widget::{canvas, svg};
|
use iced::widget::canvas;
|
||||||
|
|
||||||
const BORDER: Color = Color {
|
let btn_style = |theme: &Theme, status: button::Status| {
|
||||||
r: 0.40,
|
let palette = theme.extended_palette();
|
||||||
g: 0.40,
|
let pair = match status {
|
||||||
b: 0.40,
|
button::Status::Hovered | button::Status::Pressed => palette.background.strong,
|
||||||
a: 1.0,
|
_ => palette.background.weak,
|
||||||
};
|
};
|
||||||
const TEXT_COL: Color = Color {
|
button::Style {
|
||||||
r: 0.88,
|
background: Some(Background::Color(pair.color)),
|
||||||
g: 0.88,
|
text_color: pair.text,
|
||||||
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,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
shadow: iced::Shadow::default(),
|
shadow: iced::Shadow::default(),
|
||||||
snap: false,
|
snap: false,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let icon_btn = move |bytes: &'static [u8], msg: Message| -> Element<'static, Message> {
|
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)
|
.on_press(msg)
|
||||||
.padding(3)
|
.padding(3)
|
||||||
.style(btn_style)
|
.style(btn_style)
|
||||||
.into()
|
.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,
|
let small_input = |placeholder: &'static str,
|
||||||
val: &str,
|
val: &str,
|
||||||
on: fn(String) -> Message,
|
on: fn(String) -> Message,
|
||||||
|
|
@ -640,14 +590,17 @@ pub(super) fn mtext_editor_overlay<'a>(
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill),
|
.height(Fill),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(FIELD_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -665,8 +618,10 @@ pub(super) fn mtext_editor_overlay<'a>(
|
||||||
]
|
]
|
||||||
.align_y(iced::Alignment::Center),
|
.align_y(iced::Alignment::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(crate::ui::style::style_manager::TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -710,50 +665,10 @@ pub(super) fn viewport_context_menu_overlay(
|
||||||
last_cmds: Vec<String>,
|
last_cmds: Vec<String>,
|
||||||
draworder_open: bool,
|
draworder_open: bool,
|
||||||
) -> Element<'static, Message> {
|
) -> 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> {
|
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)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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,
|
|
||||||
})
|
|
||||||
.padding([4, 12])
|
.padding([4, 12])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -761,8 +676,10 @@ pub(super) fn viewport_context_menu_overlay(
|
||||||
|
|
||||||
let sep = || -> Element<'static, Message> {
|
let sep = || -> Element<'static, Message> {
|
||||||
container(iced::widget::Space::new().width(Fill).height(1))
|
container(iced::widget::Space::new().width(Fill).height(1))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(SEP_COL)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -773,18 +690,9 @@ pub(super) fn viewport_context_menu_overlay(
|
||||||
|
|
||||||
// Indented variant for sub-menu rows (e.g. Draw Order children).
|
// Indented variant for sub-menu rows (e.g. Draw Order children).
|
||||||
let subitem = |label: String, msg: Message| -> Element<'static, Message> {
|
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)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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,
|
|
||||||
})
|
|
||||||
.padding(iced::Padding {
|
.padding(iced::Padding {
|
||||||
top: 4.0,
|
top: 4.0,
|
||||||
right: 12.0,
|
right: 12.0,
|
||||||
|
|
@ -827,30 +735,21 @@ pub(super) fn viewport_context_menu_overlay(
|
||||||
));
|
));
|
||||||
items.push(sep());
|
items.push(sep());
|
||||||
let do_caret = if draworder_open {
|
let do_caret = if draworder_open {
|
||||||
crate::ui::icons::arrow_down(9.0, TEXT_COL)
|
crate::ui::icons::themed_arrow_down(9.0)
|
||||||
} else {
|
} else {
|
||||||
crate::ui::icons::arrow_right(9.0, TEXT_COL)
|
crate::ui::icons::themed_arrow_right(9.0)
|
||||||
};
|
};
|
||||||
items.push(
|
items.push(
|
||||||
button(
|
button(
|
||||||
row![
|
row![
|
||||||
text("Draw Order").size(12).color(TEXT_COL),
|
text("Draw Order").size(12),
|
||||||
iced::widget::Space::new().width(Fill),
|
iced::widget::Space::new().width(Fill),
|
||||||
do_caret,
|
do_caret,
|
||||||
]
|
]
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::DrawOrderSubmenuToggle)
|
.on_press(Message::DrawOrderSubmenuToggle)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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,
|
|
||||||
})
|
|
||||||
.padding([4, 12])
|
.padding([4, 12])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.into(),
|
.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_col = column(items).spacing(0).width(iced::Length::Fixed(180.0));
|
||||||
|
|
||||||
let menu = container(menu_col)
|
let menu = container(menu_col)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(MENU_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: MENU_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 0])
|
.padding([4, 0])
|
||||||
.width(iced::Length::Fixed(180.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
|
/// snap ICONS only — the names show as hover tooltips. Picking one applies
|
||||||
/// that snap to just the next point pick.
|
/// that snap to just the next point pick.
|
||||||
pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Message> {
|
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;
|
const COLS: usize = 4;
|
||||||
|
|
||||||
let cell = |snap_type: crate::snap::SnapType, label: &'static str| -> Element<'static, Message> {
|
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),
|
crate::ui::icons::osnap(snap_type),
|
||||||
16.0,
|
16.0,
|
||||||
ICON_COLOR,
|
|
||||||
))
|
))
|
||||||
.width(26)
|
.width(26)
|
||||||
.height(26)
|
.height(26)
|
||||||
|
|
@ -947,26 +832,33 @@ pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Messag
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
let btn = button(icon)
|
let btn = button(icon)
|
||||||
.on_press(Message::SnapOverridePick(snap_type))
|
.on_press(Message::SnapOverridePick(snap_type))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: matches!(
|
||||||
button::Status::Hovered | button::Status::Pressed => HOVER,
|
status,
|
||||||
_ => Color::TRANSPARENT,
|
button::Status::Hovered | button::Status::Pressed
|
||||||
})),
|
)
|
||||||
|
.then_some(Background::Color(
|
||||||
|
theme.extended_palette().primary.weak.color
|
||||||
|
)),
|
||||||
border: Border::default(),
|
border: Border::default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding(2);
|
.padding(2);
|
||||||
iced::widget::tooltip(
|
iced::widget::tooltip(
|
||||||
btn,
|
btn,
|
||||||
container(text(label).size(11).color(Color::WHITE))
|
container(text(label).size(11))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(TIP_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.strong.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: PANEL_BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
|
text_color: Some(palette.background.strong.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([2, 6]),
|
.padding([2, 6]),
|
||||||
iced::widget::tooltip::Position::Bottom,
|
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)
|
let panel = container(grid)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: PANEL_BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(4);
|
.padding(4);
|
||||||
|
|
||||||
|
|
@ -1024,49 +919,6 @@ pub(super) fn qselect_overlay<'a>(
|
||||||
properties: &[(String, String)],
|
properties: &[(String, String)],
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
use iced::widget::{checkbox, pick_list};
|
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()];
|
let mut type_options: Vec<String> = vec![QSELECT_ANY_TYPE.to_string()];
|
||||||
type_options.extend(types.iter().cloned());
|
type_options.extend(types.iter().cloned());
|
||||||
|
|
||||||
|
|
@ -1111,28 +963,33 @@ pub(super) fn qselect_overlay<'a>(
|
||||||
let label = |s: &'static str| {
|
let label = |s: &'static str| {
|
||||||
text(s)
|
text(s)
|
||||||
.size(12)
|
.size(12)
|
||||||
.color(TEXT)
|
|
||||||
.width(iced::Length::Fixed(90.0))
|
.width(iced::Length::Fixed(90.0))
|
||||||
};
|
};
|
||||||
|
|
||||||
let btn = |lbl: &'static str, msg: Message, base: Color, hov: Color| {
|
let btn = |lbl: &'static str, msg: Message, primary: bool| {
|
||||||
button(text(lbl).size(12).color(TEXT))
|
button(text(lbl).size(12))
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(move |_: &Theme, st| button::Style {
|
.style(move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(
|
let palette = theme.extended_palette();
|
||||||
if matches!(st, button::Status::Hovered | button::Status::Pressed) {
|
let pair = match (
|
||||||
hov
|
primary,
|
||||||
} else {
|
matches!(st, button::Status::Hovered | button::Status::Pressed),
|
||||||
base
|
) {
|
||||||
},
|
(true, true) => palette.primary.strong,
|
||||||
)),
|
(true, false) => palette.primary.base,
|
||||||
text_color: TEXT,
|
(false, true) => palette.background.strong,
|
||||||
|
(false, false) => palette.background.weak,
|
||||||
|
};
|
||||||
|
button::Style {
|
||||||
|
background: Some(Background::Color(pair.color)),
|
||||||
|
text_color: pair.text,
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 14])
|
.padding([4, 14])
|
||||||
};
|
};
|
||||||
|
|
@ -1143,7 +1000,7 @@ pub(super) fn qselect_overlay<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let panel_body = column![
|
let panel_body = column![
|
||||||
text("Quick Select").size(14).color(TEXT),
|
text("Quick Select").size(14),
|
||||||
Space::new().height(10),
|
Space::new().height(10),
|
||||||
row![
|
row![
|
||||||
label("Object type:"),
|
label("Object type:"),
|
||||||
|
|
@ -1198,15 +1055,15 @@ pub(super) fn qselect_overlay<'a>(
|
||||||
.on_toggle(Message::QSelectSetAppend)
|
.on_toggle(Message::QSelectSetAppend)
|
||||||
.size(14),
|
.size(14),
|
||||||
Space::new().width(6),
|
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),
|
.align_y(iced::Alignment::Center),
|
||||||
Space::new().height(14),
|
Space::new().height(14),
|
||||||
row![
|
row![
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
btn("Cancel", Message::QSelectClose, BTN_BG, BTN_HOV),
|
btn("Cancel", Message::QSelectClose, false),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
btn("Apply", Message::QSelectApply, BTN_OK, BTN_OK_HOV),
|
btn("Apply", Message::QSelectApply, true),
|
||||||
]
|
]
|
||||||
.align_y(iced::Alignment::Center),
|
.align_y(iced::Alignment::Center),
|
||||||
]
|
]
|
||||||
|
|
@ -1215,14 +1072,17 @@ pub(super) fn qselect_overlay<'a>(
|
||||||
let panel = container(panel_body)
|
let panel = container(panel_body)
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.width(iced::Length::Fixed(400.0))
|
.width(iced::Length::Fixed(400.0))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 6.0.into(),
|
radius: 6.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Outside-click catcher — fills the whole screen, sits below the
|
// 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::{
|
use iced::widget::{
|
||||||
button, container, mouse_area, pick_list, stack, Space,
|
button, container, mouse_area, pick_list, stack, Space,
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Theme};
|
use iced::{Background, Border, Element, Theme};
|
||||||
|
|
||||||
// ── Render-mode picker ──────────────────────────────────────────────────────
|
// ── Render-mode picker ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -30,16 +30,14 @@ fn vc_btn<'a>(content: Element<'a, Message>, size: f32, msg: Message) -> Element
|
||||||
)
|
)
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| iced::widget::button::Style {
|
.style(|theme: &Theme, status| iced::widget::button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: matches!(
|
||||||
iced::widget::button::Status::Hovered | iced::widget::button::Status::Pressed => Color {
|
status,
|
||||||
r: 0.45,
|
iced::widget::button::Status::Hovered | iced::widget::button::Status::Pressed
|
||||||
g: 0.62,
|
)
|
||||||
b: 0.95,
|
.then_some(Background::Color(
|
||||||
a: 0.30,
|
theme.extended_palette().primary.weak.color
|
||||||
},
|
)),
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..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> {
|
pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
||||||
use crate::scene::NudgeDir;
|
use crate::scene::NudgeDir;
|
||||||
use crate::ui::icons;
|
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 r = VIEWCUBE_REGION_PX;
|
||||||
let c = r * 0.5;
|
let c = r * 0.5;
|
||||||
let cube_half = VIEWCUBE_PX as f32 * 0.36; // VIEWCUBE_PX * VIEWCUBE_SCALE
|
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(
|
vc_place(
|
||||||
3.0,
|
3.0,
|
||||||
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(
|
vc_place(
|
||||||
rax,
|
rax,
|
||||||
ray,
|
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(
|
vc_place(
|
||||||
rbx,
|
rbx,
|
||||||
rby,
|
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(
|
vc_place(
|
||||||
tux,
|
tux,
|
||||||
tuy,
|
tuy,
|
||||||
vc_btn(
|
vc_btn(
|
||||||
icons::arrow_down(8.0, tint),
|
icons::themed_arrow_down(8.0),
|
||||||
TRI,
|
TRI,
|
||||||
Message::ViewCubeNudge(NudgeDir::Up)
|
Message::ViewCubeNudge(NudgeDir::Up)
|
||||||
)
|
)
|
||||||
|
|
@ -119,7 +111,7 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
||||||
tdx,
|
tdx,
|
||||||
tdy,
|
tdy,
|
||||||
vc_btn(
|
vc_btn(
|
||||||
icons::arrow_up(8.0, tint),
|
icons::themed_arrow_up(8.0),
|
||||||
TRI,
|
TRI,
|
||||||
Message::ViewCubeNudge(NudgeDir::Down)
|
Message::ViewCubeNudge(NudgeDir::Down)
|
||||||
)
|
)
|
||||||
|
|
@ -128,7 +120,7 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
||||||
tlx,
|
tlx,
|
||||||
tly,
|
tly,
|
||||||
vc_btn(
|
vc_btn(
|
||||||
icons::arrow_right(8.0, tint),
|
icons::themed_arrow_right(8.0),
|
||||||
TRI,
|
TRI,
|
||||||
Message::ViewCubeNudge(NudgeDir::Left)
|
Message::ViewCubeNudge(NudgeDir::Left)
|
||||||
)
|
)
|
||||||
|
|
@ -137,7 +129,7 @@ pub(super) fn viewcube_nav_controls<'a>() -> Element<'a, Message> {
|
||||||
trx,
|
trx,
|
||||||
try_,
|
try_,
|
||||||
vc_btn(
|
vc_btn(
|
||||||
icons::arrow_left(8.0, tint),
|
icons::themed_arrow_left(8.0),
|
||||||
TRI,
|
TRI,
|
||||||
Message::ViewCubeNudge(NudgeDir::Right)
|
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.
|
/// The WCS / named-UCS selector shown under the cube.
|
||||||
pub(super) fn viewcube_ucs_picker<'a>(current: String, names: Vec<String>) -> Element<'a, Message> {
|
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()];
|
let mut options = vec!["WCS".to_string()];
|
||||||
options.extend(names);
|
options.extend(names);
|
||||||
let selected = if current.is_empty() {
|
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
|
// 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).
|
// simple half-width offset (content-sized width would drift off-centre).
|
||||||
.width(iced::Length::Fixed(UCS_PICKER_W))
|
.width(iced::Length::Fixed(UCS_PICKER_W))
|
||||||
.style(move |_: &Theme, _| iced::widget::pick_list::Style {
|
.style(move |theme: &Theme, _| {
|
||||||
background: Background::Color(Color {
|
let palette = theme.extended_palette();
|
||||||
r: 0.16,
|
iced::widget::pick_list::Style {
|
||||||
g: 0.17,
|
background: Background::Color(palette.background.weak.color),
|
||||||
b: 0.20,
|
|
||||||
a: 0.92,
|
|
||||||
}),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
|
color: palette.background.neutral.color,
|
||||||
|
width: 1.0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text_color: light,
|
text_color: palette.background.base.text,
|
||||||
placeholder_color: light,
|
placeholder_color: palette.background.base.text.scale_alpha(0.68),
|
||||||
handle_color: light,
|
handle_color: palette.background.base.text,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,25 +20,6 @@ pub struct ColorExtras {
|
||||||
pub by_block: bool,
|
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
|
/// 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).
|
/// (ByBlock=0, ByLayer=256, indexed 1-255; RGB has no ACI slot → ByLayer).
|
||||||
pub fn color_to_aci_string(c: AcadColor) -> String {
|
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.
|
/// A small colour square.
|
||||||
fn swatch<'a>(bg: Color) -> Element<'a, Message> {
|
fn swatch<'a>(bg: Color) -> Element<'a, Message> {
|
||||||
container(text("").width(13).height(13))
|
container(text("").width(13).height(13))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(bg)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: Color {
|
color: theme.extended_palette().background.neutral.color,
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.5,
|
|
||||||
},
|
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -126,8 +102,8 @@ pub fn color_selector<'a>(
|
||||||
let head = button(
|
let head = button(
|
||||||
row![
|
row![
|
||||||
swatch(cur_bg),
|
swatch(cur_bg),
|
||||||
text(cur_name).size(11).color(TEXT),
|
text(cur_name).size(11),
|
||||||
crate::ui::icons::arrow_toggle(open, 9.0, TEXT),
|
crate::ui::icons::themed_arrow_toggle(open, 9.0),
|
||||||
]
|
]
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -141,14 +117,17 @@ pub fn color_selector<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let popup = container(color_list(extras, on_select, on_more))
|
let popup = container(color_list(extras, on_select, on_more))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PICKER_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.width(220);
|
.width(220);
|
||||||
|
|
@ -172,19 +151,15 @@ pub fn color_list<'a>(
|
||||||
let named_row = |color: AcadColor| -> Element<'a, Message> {
|
let named_row = |color: AcadColor| -> Element<'a, Message> {
|
||||||
let (bg, name) = acad_color_display(color);
|
let (bg, name) = acad_color_display(color);
|
||||||
button(
|
button(
|
||||||
row![swatch(bg), text(name).size(11).color(TEXT)]
|
row![swatch(bg), text(name).size(11)]
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(on_select(color))
|
.on_press(on_select(color))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| button::Style {
|
||||||
background: matches!(status, button::Status::Hovered)
|
background: matches!(status, button::Status::Hovered).then_some(
|
||||||
.then_some(Background::Color(Color {
|
Background::Color(theme.extended_palette().background.strong.color)
|
||||||
r: 0.25,
|
),
|
||||||
g: 0.25,
|
|
||||||
b: 0.30,
|
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding([2, 4])
|
.padding([2, 4])
|
||||||
|
|
@ -203,16 +178,12 @@ pub fn color_list<'a>(
|
||||||
list = list.push(named_row(AcadColor::Index(i)));
|
list = list.push(named_row(AcadColor::Index(i)));
|
||||||
}
|
}
|
||||||
list = list.push(
|
list = list.push(
|
||||||
button(text("More…").size(11).color(TEXT))
|
button(text("More…").size(11))
|
||||||
.on_press(on_more)
|
.on_press(on_more)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| button::Style {
|
||||||
background: matches!(status, button::Status::Hovered)
|
background: matches!(status, button::Status::Hovered).then_some(
|
||||||
.then_some(Background::Color(Color {
|
Background::Color(theme.extended_palette().background.strong.color)
|
||||||
r: 0.25,
|
),
|
||||||
g: 0.25,
|
|
||||||
b: 0.30,
|
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding([2, 4])
|
.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 chip = |color: AcadColor, label: &'static str| -> Element<'static, Message> {
|
||||||
let (bg, _) = acad_color_display(color);
|
let (bg, _) = acad_color_display(color);
|
||||||
button(
|
button(
|
||||||
row![swatch(bg), text(label).size(11).color(TEXT)]
|
row![swatch(bg), text(label).size(11)]
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
|
|
@ -250,18 +221,13 @@ pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'sta
|
||||||
r = r.push(
|
r = r.push(
|
||||||
button(text("").width(18).height(18))
|
button(text("").width(18).height(18))
|
||||||
.on_press(on_pick(AcadColor::Index(ci)))
|
.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)),
|
background: Some(Background::Color(bg)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: if matches!(status, button::Status::Hovered) {
|
color: if matches!(status, button::Status::Hovered) {
|
||||||
Color::WHITE
|
theme.extended_palette().primary.base.color
|
||||||
} else {
|
} else {
|
||||||
Color {
|
theme.extended_palette().background.neutral.color
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.4,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
width: if matches!(status, button::Status::Hovered) {
|
width: if matches!(status, button::Status::Hovered) {
|
||||||
1.5
|
1.5
|
||||||
|
|
@ -281,14 +247,16 @@ pub fn color_grid_window(on_pick: impl Fn(AcadColor) -> Message) -> Element<'sta
|
||||||
|
|
||||||
container(
|
container(
|
||||||
column![
|
column![
|
||||||
text("Select Color").size(13).color(TEXT),
|
text("Select Color").size(13),
|
||||||
row![chip(AcadColor::ByLayer, "ByLayer"), chip(AcadColor::ByBlock, "ByBlock")].spacing(6),
|
row![chip(AcadColor::ByLayer, "ByLayer"), chip(AcadColor::ByBlock, "ByBlock")].spacing(6),
|
||||||
scrollable(grid).height(Length::Fill),
|
scrollable(grid).height(Length::Fill),
|
||||||
]
|
]
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(PICKER_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding(10)
|
.padding(10)
|
||||||
|
|
|
||||||
|
|
@ -370,11 +370,14 @@ impl CommandLine {
|
||||||
let history_rows = visible[start..]
|
let history_rows = visible[start..]
|
||||||
.iter()
|
.iter()
|
||||||
.fold(column![].spacing(0), |col, entry| {
|
.fold(column![].spacing(0), |col, entry| {
|
||||||
let color = match entry.kind {
|
let kind = entry.kind.clone();
|
||||||
EntryKind::Command => CMD_COLOR,
|
let entry_text = |value: String| {
|
||||||
EntryKind::Output => OUT_COLOR,
|
text(value).size(11).style({
|
||||||
EntryKind::Error => ERR_COLOR,
|
let kind = kind.clone();
|
||||||
EntryKind::Info => INFO_COLOR,
|
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
|
// The current step's prompt is the single pinned line. When the
|
||||||
// step offers options, render them as clickable buttons inline
|
// 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).
|
// listing is dropped here (the history log keeps the full text).
|
||||||
if entry.pinned && !self.step_options.is_empty() {
|
if entry.pinned && !self.step_options.is_empty() {
|
||||||
let shown = strip_option_listing(&entry.text);
|
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)
|
.spacing(6)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
for opt in &self.step_options {
|
for opt in &self.step_options {
|
||||||
let btn = button(
|
let btn = button(text(opt.label.to_uppercase()).size(11))
|
||||||
text(opt.label.to_uppercase()).size(11).color(CMD_COLOR),
|
|
||||||
)
|
|
||||||
.on_press(Message::CommandOptionPick(opt.keyword.clone()))
|
.on_press(Message::CommandOptionPick(opt.keyword.clone()))
|
||||||
.padding([1, 6])
|
.padding([1, 6])
|
||||||
.style(|_: &Theme, status| {
|
.style(|theme: &Theme, status| {
|
||||||
let bg = if matches!(status, button::Status::Hovered) {
|
let palette = theme.extended_palette();
|
||||||
Color {
|
let pair = if matches!(
|
||||||
r: 0.28,
|
status,
|
||||||
g: 0.40,
|
button::Status::Hovered | button::Status::Pressed
|
||||||
b: 0.56,
|
) {
|
||||||
a: 1.0,
|
palette.primary.weak
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
INPUT_ROW_BG
|
palette.background.weakest
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
text_color: Color::WHITE,
|
text_color: pair.text,
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -419,46 +419,41 @@ impl CommandLine {
|
||||||
}
|
}
|
||||||
col.push(container(r).padding([1, 8]))
|
col.push(container(r).padding([1, 8]))
|
||||||
} else {
|
} 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
|
// Literal-space toggle: while active, every line behaves as if it
|
||||||
// started with `>` — Space stays in the line instead of submitting, so
|
// started with `>` — Space stays in the line instead of submitting, so
|
||||||
// arguments with spaces (text strings, paths, `UCS Z 90` as one line)
|
// arguments with spaces (text strings, paths, `UCS Z 90` as one line)
|
||||||
// can be typed. Persists until toggled off; a hand-typed leading `>`
|
// can be typed. Persists until toggled off; a hand-typed leading `>`
|
||||||
// lights the button too (same mode, one line only).
|
// lights the button too (same mode, one line only).
|
||||||
let literal_active = self.literal_spaces || self.input.starts_with('>');
|
let literal_active = self.literal_spaces || self.input.starts_with('>');
|
||||||
let literal_btn = button(text(">").size(11).color(if literal_active {
|
let literal_btn = button(text(">").size(11))
|
||||||
Color::WHITE
|
|
||||||
} else {
|
|
||||||
PROMPT_COLOR
|
|
||||||
}))
|
|
||||||
.on_press(Message::CommandLiteralToggle)
|
.on_press(Message::CommandLiteralToggle)
|
||||||
.padding([2, 6])
|
.padding([2, 6])
|
||||||
.style(move |_: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
let bg = if literal_active {
|
let palette = theme.extended_palette();
|
||||||
Color {
|
let pair = if literal_active {
|
||||||
r: 0.28,
|
palette.primary.weak
|
||||||
g: 0.40,
|
} else if matches!(
|
||||||
b: 0.56,
|
status,
|
||||||
a: 1.0,
|
button::Status::Hovered | button::Status::Pressed
|
||||||
}
|
) {
|
||||||
} else if matches!(status, button::Status::Hovered) {
|
palette.background.weak
|
||||||
Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.30,
|
|
||||||
b: 0.42,
|
|
||||||
a: 1.0,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
INPUT_ROW_BG
|
palette.background.weakest
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
text_color: Color::WHITE,
|
text_color: pair.text,
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -467,19 +462,10 @@ impl CommandLine {
|
||||||
});
|
});
|
||||||
let literal_tip = container(
|
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.")
|
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)
|
.size(11),
|
||||||
.color(Color::WHITE),
|
|
||||||
)
|
)
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box);
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER_COLOR,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let literal_btn = tooltip(literal_btn, literal_tip, tooltip::Position::Top).gap(4);
|
let literal_btn = tooltip(literal_btn, literal_tip, tooltip::Position::Top).gap(4);
|
||||||
// While dynamic input is capturing keystrokes, the command-line
|
// While dynamic input is capturing keystrokes, the command-line
|
||||||
// text field is left without an `on_input` handler so it can't
|
// text field is left without an `on_input` handler so it can't
|
||||||
|
|
@ -492,33 +478,6 @@ impl CommandLine {
|
||||||
.on_submit(Message::CommandSubmit);
|
.on_submit(Message::CommandSubmit);
|
||||||
}
|
}
|
||||||
let input = input
|
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)
|
.size(11)
|
||||||
.padding([4, 6]);
|
.padding([4, 6]);
|
||||||
// Autocomplete suggestions panel, shown above the input row
|
// Autocomplete suggestions panel, shown above the input row
|
||||||
|
|
@ -537,31 +496,25 @@ impl CommandLine {
|
||||||
let mut col = column![].spacing(0).width(Length::Fill);
|
let mut col = column![].spacing(0).width(Length::Fill);
|
||||||
for (idx, cmd) in matches.iter().enumerate() {
|
for (idx, cmd) in matches.iter().enumerate() {
|
||||||
let is_selected = cursor == idx;
|
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()))
|
.on_press(Message::CommandSuggestionPick(cmd.clone()))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([2, 8])
|
.padding([2, 8])
|
||||||
.style(move |_: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
let bg = if is_selected {
|
let palette = theme.extended_palette();
|
||||||
Color {
|
let pair = if is_selected {
|
||||||
r: 0.28,
|
palette.primary.weak
|
||||||
g: 0.40,
|
} else if matches!(
|
||||||
b: 0.56,
|
status,
|
||||||
a: 1.0,
|
button::Status::Hovered | button::Status::Pressed
|
||||||
}
|
) {
|
||||||
} else if matches!(status, button::Status::Hovered) {
|
palette.background.weak
|
||||||
Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.30,
|
|
||||||
b: 0.42,
|
|
||||||
a: 1.0,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
PANEL_BG
|
palette.background.base
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
text_color: Color::WHITE,
|
text_color: pair.text,
|
||||||
border: Border::default(),
|
border: Border::default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
@ -569,15 +522,7 @@ impl CommandLine {
|
||||||
col = col.push(row);
|
col = col.push(row);
|
||||||
}
|
}
|
||||||
container(col)
|
container(col)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER_COLOR,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -590,22 +535,13 @@ impl CommandLine {
|
||||||
// user can recover anything that has already faded off the
|
// user can recover anything that has already faded off the
|
||||||
// overlay.
|
// overlay.
|
||||||
let dropdown_icon = if self.history_open {
|
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 {
|
} else {
|
||||||
crate::ui::icons::arrow_right(11.0, PROMPT_COLOR)
|
crate::ui::icons::themed_arrow_right(11.0)
|
||||||
};
|
};
|
||||||
let dropdown_btn = button(dropdown_icon)
|
let dropdown_btn = button(dropdown_icon)
|
||||||
.on_press(Message::CommandHistoryToggle)
|
.on_press(Message::CommandHistoryToggle)
|
||||||
.style(|_: &Theme, _status| button::Style {
|
.style(button::subtle)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([2, 6]);
|
.padding([2, 6]);
|
||||||
let input_row = row![prompt, literal_btn, input, dropdown_btn]
|
let input_row = row![prompt, literal_btn, input, dropdown_btn]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
|
|
@ -625,23 +561,21 @@ impl CommandLine {
|
||||||
.size(11)
|
.size(11)
|
||||||
.padding([2, 8])
|
.padding([2, 8])
|
||||||
.max_height(180.0)
|
.max_height(180.0)
|
||||||
.style(|_: &Theme, _status| text_editor::Style {
|
.style(|theme: &Theme, _status| {
|
||||||
background: Background::Color(PANEL_BG),
|
let palette = theme.extended_palette();
|
||||||
border: Border::default(),
|
text_editor::Style {
|
||||||
placeholder: OUT_COLOR,
|
background: Background::Color(palette.background.base.color),
|
||||||
value: CMD_COLOR,
|
border: Border::default(),
|
||||||
selection: Color {
|
placeholder: palette.background.base.text.scale_alpha(0.72),
|
||||||
r: 0.20,
|
value: palette.background.base.text,
|
||||||
g: 0.44,
|
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||||
b: 0.72,
|
}
|
||||||
a: 0.5,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// Header strip: a Copy-all and a Clear button pinned above the log.
|
// Header strip: a Copy-all and a Clear button pinned above the log.
|
||||||
let copy_btn = button(
|
let copy_btn = button(
|
||||||
row![
|
row![
|
||||||
crate::ui::icons::tinted(crate::ui::icons::COPY, 11.0, PROMPT_COLOR),
|
crate::ui::icons::themed_success(crate::ui::icons::COPY, 11.0),
|
||||||
text("Copy").size(11).color(CMD_COLOR),
|
text("Copy").size(11),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -651,8 +585,8 @@ impl CommandLine {
|
||||||
.padding([2, 6]);
|
.padding([2, 6]);
|
||||||
let clear_btn = button(
|
let clear_btn = button(
|
||||||
row![
|
row![
|
||||||
crate::ui::icons::tinted(crate::ui::icons::TRASH, 11.0, ERR_COLOR),
|
crate::ui::icons::themed_warning(crate::ui::icons::TRASH, 11.0),
|
||||||
text("Clear").size(11).color(CMD_COLOR),
|
text("Clear").size(11),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -668,15 +602,7 @@ impl CommandLine {
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([2, 6]);
|
.padding([2, 6]);
|
||||||
let panel = container(column![header, log])
|
let panel = container(column![header, log])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER_COLOR,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([4, 0]);
|
.padding([4, 0]);
|
||||||
opaque(panel).into()
|
opaque(panel).into()
|
||||||
|
|
@ -688,35 +614,43 @@ impl CommandLine {
|
||||||
autocomplete,
|
autocomplete,
|
||||||
dropdown,
|
dropdown,
|
||||||
container(history_rows)
|
container(history_rows)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(HISTORY_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([2, 0]),
|
.padding([2, 0]),
|
||||||
container(input_row)
|
container(input_row)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(INPUT_ROW_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weakest.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into()
|
radius: 3.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
// Match the drawing tab bar / status bar height, and vertically
|
// Match the drawing tab bar / status bar height, and vertically
|
||||||
// centre the prompt/input/dropdown within it (issue #216).
|
// centre the prompt/input/dropdown within it (issue #216).
|
||||||
.center_y(Length::Fixed(30.0)),
|
.center_y(Length::Fixed(30.0)),
|
||||||
])
|
])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fixed(720.0))
|
.width(Length::Fixed(720.0))
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -780,22 +714,18 @@ pub fn ranked_matches(
|
||||||
|
|
||||||
/// Flat button style for the history dropdown's Copy / Clear strip: a subtle
|
/// Flat button style for the history dropdown's Copy / Clear strip: a subtle
|
||||||
/// filled pill that brightens on hover.
|
/// filled pill that brightens on hover.
|
||||||
fn header_btn_style(_: &Theme, status: button::Status) -> button::Style {
|
fn header_btn_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||||
let bg = if matches!(status, button::Status::Hovered) {
|
let palette = theme.extended_palette();
|
||||||
Color {
|
let pair = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
||||||
r: 0.24,
|
palette.background.weak
|
||||||
g: 0.24,
|
|
||||||
b: 0.24,
|
|
||||||
a: 1.0,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
INPUT_ROW_BG
|
palette.background.weakest
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
text_color: Color::WHITE,
|
text_color: pair.text,
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -803,66 +733,15 @@ fn header_btn_style(_: &Theme, status: button::Status) -> button::Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PANEL_BG: Color = Color {
|
fn history_color(theme: &Theme, kind: &EntryKind) -> Color {
|
||||||
r: 0.15,
|
let palette = theme.extended_palette();
|
||||||
g: 0.15,
|
match kind {
|
||||||
b: 0.15,
|
EntryKind::Command => palette.background.base.text,
|
||||||
a: 1.0,
|
EntryKind::Output => palette.background.base.text.scale_alpha(0.72),
|
||||||
};
|
EntryKind::Error => palette.danger.base.color,
|
||||||
const HISTORY_BG: Color = Color {
|
EntryKind::Info => palette.primary.base.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,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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.
|
//! boxes. Drawing them from SVG instead makes the chrome font-independent.
|
||||||
|
|
||||||
use iced::widget::{container, svg, Space};
|
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_DOWN: &[u8] = include_bytes!("../../assets/icons/ui/tri_down.svg");
|
||||||
const TRI_UP: &[u8] = include_bytes!("../../assets/icons/ui/tri_up.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 PRINT: &[u8] = include_bytes!("../../assets/icons/ui/print.svg");
|
||||||
pub const HEART: &[u8] = include_bytes!("../../assets/icons/ui/heart.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 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");
|
pub const ARROW_LONG_RIGHT: &[u8] = include_bytes!("../../assets/icons/ui/arrow_long_right.svg");
|
||||||
|
|
||||||
// ── Status-bar toggle icons (issue #216) ──────────────────────────────────
|
// ── 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_SELCYCLE: &[u8] = include_bytes!("../../assets/icons/status/selcycle.svg");
|
||||||
pub const ST_CLEANSCREEN: &[u8] = include_bytes!("../../assets/icons/status/cleanscreen.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`.
|
/// Render a chrome icon with the active Iced theme's normal text color.
|
||||||
pub fn tinted<'a, M: 'a>(bytes: &'static [u8], size: f32, color: Color) -> Element<'a, M> {
|
pub fn themed<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||||
svg(svg::Handle::from_memory(bytes))
|
svg(svg::Handle::from_memory(bytes))
|
||||||
.width(size)
|
.width(size)
|
||||||
.height(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()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Backwards-compatible alias used by the caret/undo/redo helpers below.
|
/// Render secondary chrome with the active Iced theme's text color.
|
||||||
fn icon<'a, M: 'a>(bytes: &'static [u8], size: f32, color: Color) -> Element<'a, M> {
|
pub fn themed_secondary<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||||
tinted(bytes, size, color)
|
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
|
/// Render disabled chrome with the active Iced theme's text color.
|
||||||
/// when `active`, otherwise an empty spacer that preserves alignment. Used by
|
pub fn themed_disabled<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> {
|
||||||
/// the many dropdown / popup list rows that mark the selected entry.
|
svg(svg::Handle::from_memory(bytes))
|
||||||
pub fn check_cell<'a, M: 'a>(active: bool, color: Color) -> Element<'a, M> {
|
.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 {
|
let inner: Element<'a, M> = if active {
|
||||||
tinted(CHECK, 11.0, color)
|
themed_primary(CHECK, 11.0)
|
||||||
} else {
|
} else {
|
||||||
Space::new().width(0).into()
|
Space::new().width(0).into()
|
||||||
};
|
};
|
||||||
|
|
@ -166,46 +251,59 @@ pub fn layer_lock(locked: bool) -> &'static [u8] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downward dropdown caret (replaces `▾`).
|
pub fn themed_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||||
pub fn arrow_down<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
themed(TRI_DOWN, size)
|
||||||
icon(TRI_DOWN, size, color)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upward dropdown caret, shown when a dropdown is open (replaces `▲`).
|
pub fn themed_arrow_up<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||||
pub fn arrow_up<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
themed(TRI_UP, size)
|
||||||
icon(TRI_UP, size, color)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rightward caret for a collapsed item (replaces `▸`).
|
pub fn themed_arrow_right<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||||
pub fn arrow_right<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
themed(TRI_RIGHT, size)
|
||||||
icon(TRI_RIGHT, size, color)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Leftward caret.
|
pub fn themed_arrow_left<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||||
pub fn arrow_left<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
themed(TRI_LEFT, size)
|
||||||
icon(TRI_LEFT, size, color)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// House glyph — the ViewCube "home view" button.
|
pub fn themed_primary_arrow_down<'a, M: 'a>(size: f32) -> Element<'a, M> {
|
||||||
pub fn home<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
themed_primary(TRI_DOWN, size)
|
||||||
icon(HOME, size, color)
|
}
|
||||||
|
|
||||||
|
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`.
|
/// 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 {
|
if open {
|
||||||
arrow_up(size, color)
|
themed_arrow_up(size)
|
||||||
} else {
|
} else {
|
||||||
arrow_down(size, color)
|
themed_arrow_down(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undo curved arrow (replaces `↶`).
|
pub fn themed_undo<'a, M: 'a>(size: f32, enabled: bool) -> Element<'a, M> {
|
||||||
pub fn undo<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
if enabled {
|
||||||
icon(UNDO, size, color)
|
themed(UNDO, size)
|
||||||
|
} else {
|
||||||
|
themed_disabled(UNDO, size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redo curved arrow (replaces `↷`).
|
pub fn themed_redo<'a, M: 'a>(size: f32, enabled: bool) -> Element<'a, M> {
|
||||||
pub fn redo<'a, M: 'a>(size: f32, color: Color) -> Element<'a, M> {
|
if enabled {
|
||||||
icon(REDO, size, color)
|
themed(REDO, size)
|
||||||
|
} else {
|
||||||
|
themed_disabled(REDO, size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,34 +7,7 @@
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, mouse_area, opaque, row, stack};
|
use iced::widget::{button, column, container, mouse_area, opaque, row, stack};
|
||||||
use iced::{Background, Border, Color, Element, Length, Padding, Theme, Vector};
|
use iced::{Background, Border, 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Stack `content` over `base` behind a dimmed backdrop, framed with a
|
/// Stack `content` over `base` behind a dimmed backdrop, framed with a
|
||||||
/// draggable title bar (the ✕ close button at its right end). The backdrop only
|
/// draggable title bar (the ✕ close button at its right end). The backdrop only
|
||||||
|
|
@ -52,15 +25,9 @@ pub fn modal<'a>(
|
||||||
offset: Vector,
|
offset: Vector,
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let close = button(crate::ui::icons::tinted(
|
let close = button(crate::ui::icons::themed_danger(
|
||||||
crate::ui::icons::CLOSE,
|
crate::ui::icons::CLOSE,
|
||||||
13.0,
|
13.0,
|
||||||
Color {
|
|
||||||
r: 0.85,
|
|
||||||
g: 0.85,
|
|
||||||
b: 0.85,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
.on_press(on_close)
|
.on_press(on_close)
|
||||||
.padding([1, 7])
|
.padding([1, 7])
|
||||||
|
|
@ -71,10 +38,12 @@ pub fn modal<'a>(
|
||||||
// screen width — the dialog stays sized to its content. Pressing the grip
|
// screen width — the dialog stays sized to its content. Pressing the grip
|
||||||
// starts a drag (handled in `update`).
|
// starts a drag (handled in `update`).
|
||||||
let grip = mouse_area(
|
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])
|
.padding([1, 7])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TITLE_C)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -89,12 +58,7 @@ pub fn modal<'a>(
|
||||||
// overlaid at the right edge. The bar takes an explicit `title_width`
|
// 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 caller's content width) instead of `Fill` — a Fill child inside
|
||||||
// the Shrink frame would blow the dialog out to the full screen.
|
// the Shrink frame would blow the dialog out to the full screen.
|
||||||
let title_text = iced::widget::text(title).size(15).color(Color {
|
let title_text = iced::widget::text(title).size(15);
|
||||||
r: 0.88,
|
|
||||||
g: 0.88,
|
|
||||||
b: 0.88,
|
|
||||||
a: 1.0,
|
|
||||||
});
|
|
||||||
let title_bar = stack![
|
let title_bar = stack![
|
||||||
container(title_text)
|
container(title_text)
|
||||||
.width(Length::Fixed(title_width))
|
.width(Length::Fixed(title_width))
|
||||||
|
|
@ -108,10 +72,12 @@ pub fn modal<'a>(
|
||||||
.align_y(iced::alignment::Vertical::Center),
|
.align_y(iced::alignment::Vertical::Center),
|
||||||
];
|
];
|
||||||
|
|
||||||
let panel_style = |_: &Theme| container::Style {
|
let panel_style = |theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(PANEL)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_C,
|
color: theme.extended_palette().background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 6.0.into(),
|
radius: 6.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -125,7 +91,7 @@ pub fn modal<'a>(
|
||||||
// top-right above the content either way (`align_x(Right)`).
|
// top-right above the content either way (`align_x(Right)`).
|
||||||
let body = if resizable {
|
let body = if resizable {
|
||||||
let resize = mouse_area(
|
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]),
|
.padding([0, 2]),
|
||||||
)
|
)
|
||||||
.on_press(Message::ModalResizeGrab)
|
.on_press(Message::ModalResizeGrab)
|
||||||
|
|
@ -157,11 +123,15 @@ pub fn modal<'a>(
|
||||||
.center_x(Length::Fill)
|
.center_x(Length::Fill)
|
||||||
.center_y(Length::Fill)
|
.center_y(Length::Fill)
|
||||||
.padding(pad)
|
.padding(pad)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(Color {
|
background: Some(Background::Color(
|
||||||
a: 0.55,
|
theme
|
||||||
..Color::BLACK
|
.extended_palette()
|
||||||
})),
|
.background
|
||||||
|
.strongest
|
||||||
|
.color
|
||||||
|
.scale_alpha(0.55),
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -177,24 +147,15 @@ pub fn modal<'a>(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_style(_: &Theme, status: button::Status) -> button::Style {
|
fn close_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||||
let bg = match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered | button::Status::Pressed => Color {
|
let pair = match status {
|
||||||
r: 0.7,
|
button::Status::Hovered | button::Status::Pressed => palette.danger.strong,
|
||||||
g: 0.2,
|
_ => palette.background.weakest,
|
||||||
b: 0.2,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color {
|
|
||||||
r: 0.25,
|
|
||||||
g: 0.25,
|
|
||||||
b: 0.25,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
text_color: Color::WHITE,
|
text_color: pair.text,
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ struct SelectionCanvas {
|
||||||
hover_locked: bool,
|
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 sp = grip.pos;
|
||||||
let h = crate::scene::pick::grip::GRIP_HALF_PX;
|
let h = crate::scene::pick::grip::GRIP_HALF_PX;
|
||||||
let path = match grip.shape {
|
let path = match grip.shape {
|
||||||
|
|
@ -332,30 +332,13 @@ fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if grip.is_hot {
|
if grip.is_hot {
|
||||||
frame.fill(
|
frame.fill(&path, theme.extended_palette().danger.base.color);
|
||||||
&path,
|
|
||||||
Color {
|
|
||||||
r: 1.0,
|
|
||||||
g: 0.15,
|
|
||||||
b: 0.10,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let color = Color {
|
let palette = theme.extended_palette();
|
||||||
r: 0.10,
|
let color = palette.primary.base.color;
|
||||||
g: 0.45,
|
|
||||||
b: 0.90,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
frame.fill(
|
frame.fill(
|
||||||
&path,
|
&path,
|
||||||
Color {
|
palette.background.base.color.scale_alpha(0.7),
|
||||||
r: 0.10,
|
|
||||||
g: 0.10,
|
|
||||||
b: 0.20,
|
|
||||||
a: 0.7,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&path,
|
&path,
|
||||||
|
|
@ -442,7 +425,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
&self,
|
&self,
|
||||||
_state: &(),
|
_state: &(),
|
||||||
renderer: &iced::Renderer,
|
renderer: &iced::Renderer,
|
||||||
_theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: iced::Rectangle,
|
bounds: iced::Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) -> 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
|
// Filled bars in the pane_grid spacing gaps, so adjacent panes read as
|
||||||
// distinct viewports. Drawn first so all other overlays sit on top.
|
// distinct viewports. Drawn first so all other overlays sit on top.
|
||||||
if !self.dividers.is_empty() {
|
if !self.dividers.is_empty() {
|
||||||
const DIVIDER: Color = Color {
|
let divider = theme.extended_palette().background.neutral.color;
|
||||||
r: 0.46,
|
|
||||||
g: 0.52,
|
|
||||||
b: 0.62,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
for d in &self.dividers {
|
for d in &self.dividers {
|
||||||
let bar = canvas::Path::rectangle(
|
let bar = canvas::Path::rectangle(
|
||||||
Point::new(d.x, d.y),
|
Point::new(d.x, d.y),
|
||||||
iced::Size::new(d.width.max(1.0), d.height.max(1.0)),
|
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
|
// under the cursor, and drag a translucent ghost card along the cursor
|
||||||
// so the pane is visibly "moving".
|
// so the pane is visibly "moving".
|
||||||
if let Some(src) = self.pane_move_rect {
|
if let Some(src) = self.pane_move_rect {
|
||||||
let accent = Color {
|
let accent = theme.extended_palette().primary.base.color;
|
||||||
r: 0.30,
|
|
||||||
g: 0.62,
|
|
||||||
b: 1.0,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
// Source pane: dimmed + dashed-feel outline (it has been lifted).
|
// Source pane: dimmed + dashed-feel outline (it has been lifted).
|
||||||
let src_path =
|
let src_path =
|
||||||
canvas::Path::rectangle(Point::new(src.x, src.y), iced::Size::new(src.width, src.height));
|
canvas::Path::rectangle(Point::new(src.x, src.y), iced::Size::new(src.width, src.height));
|
||||||
frame.fill(
|
frame.fill(
|
||||||
&src_path,
|
&src_path,
|
||||||
Color {
|
theme.extended_palette().background.strong.color.scale_alpha(0.28),
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.28,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&src_path,
|
&src_path,
|
||||||
|
|
@ -539,18 +507,20 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
// Draw a selection marquee (green crossing / blue window) as a filled,
|
// Draw a selection marquee (green crossing / blue window) as a filled,
|
||||||
// stroked rectangle between two canvas points. Shared by the live
|
// stroked rectangle between two canvas points. Shared by the live
|
||||||
// box-selection and the preview-only window marquee (#291).
|
// box-selection and the preview-only window marquee (#291).
|
||||||
fn draw_marquee(frame: &mut canvas::Frame, a: Point, b: Point, crossing: bool) {
|
fn draw_marquee(
|
||||||
let (fill, stroke) = if crossing {
|
frame: &mut canvas::Frame,
|
||||||
(
|
a: Point,
|
||||||
Color { r: 0.20, g: 0.72, b: 0.44, a: 0.12 },
|
b: Point,
|
||||||
Color { r: 0.20, g: 0.72, b: 0.44, a: 0.9 },
|
crossing: bool,
|
||||||
)
|
theme: &Theme,
|
||||||
|
) {
|
||||||
|
let base = if crossing {
|
||||||
|
theme.extended_palette().success.base.color
|
||||||
} else {
|
} else {
|
||||||
(
|
theme.extended_palette().primary.base.color
|
||||||
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 },
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
let fill = base.scale_alpha(0.12);
|
||||||
|
let stroke = base.scale_alpha(0.9);
|
||||||
let x0 = a.x.min(b.x);
|
let x0 = a.x.min(b.x);
|
||||||
let y0 = a.y.min(b.y);
|
let y0 = a.y.min(b.y);
|
||||||
let w = (a.x - b.x).abs();
|
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) {
|
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.
|
// Preview marquee for point-picked windows (STRETCH) — same look, no pick.
|
||||||
if let Some((a, b, crossing)) = self.selection.preview_box {
|
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 {
|
if self.selection.poly_active && self.selection.poly_points.len() > 1 {
|
||||||
let (fill, stroke) = if self.selection.poly_crossing {
|
let base = if self.selection.poly_crossing {
|
||||||
(
|
theme.extended_palette().success.base.color
|
||||||
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,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(
|
theme.extended_palette().primary.base.color
|
||||||
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,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
let fill = base.scale_alpha(0.12);
|
||||||
|
let stroke = base.scale_alpha(0.9);
|
||||||
if let Some(cur) = self.selection.last_move_pos {
|
if let Some(cur) = self.selection.last_move_pos {
|
||||||
let start = self.selection.poly_points[0];
|
let start = self.selection.poly_points[0];
|
||||||
let fill_path = canvas::Path::new(|p| {
|
let fill_path = canvas::Path::new(|p| {
|
||||||
|
|
@ -667,7 +613,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
};
|
};
|
||||||
frame.with_clip(grip_clip, |frame| {
|
frame.with_clip(grip_clip, |frame| {
|
||||||
for grip in &self.grips {
|
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.
|
// PAN mode replaces the crosshair with a hand cursor.
|
||||||
if !over_viewcube && !over_divider && !self.pan_mode && !self.suppressed {
|
if !over_viewcube && !over_divider && !self.pan_mode && !self.suppressed {
|
||||||
if let Some(cp) = self.selection.last_move_pos {
|
if let Some(cp) = self.selection.last_move_pos {
|
||||||
let color = Color {
|
let color = theme
|
||||||
r: 0.85,
|
.extended_palette()
|
||||||
g: 0.85,
|
.background
|
||||||
b: 0.85,
|
.base
|
||||||
a: 0.90,
|
.text
|
||||||
};
|
.scale_alpha(0.90);
|
||||||
let stroke = canvas::Stroke {
|
let stroke = canvas::Stroke {
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
style: canvas::Style::Solid(color),
|
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
|
// the hovered object sits on a locked layer (issue: locked
|
||||||
// objects are visible + snappable but not selectable/editable).
|
// objects are visible + snappable but not selectable/editable).
|
||||||
if self.hover_locked {
|
if self.hover_locked {
|
||||||
let amber = Color { r: 0.96, g: 0.76, b: 0.26, a: 0.98 };
|
let warning = theme.extended_palette().warning.base;
|
||||||
let dark = Color { r: 0.12, g: 0.10, b: 0.04, a: 1.0 };
|
let amber = warning.color.scale_alpha(0.98);
|
||||||
|
let dark = warning.text;
|
||||||
let bx = cp.x + sq + 7.0;
|
let bx = cp.x + sq + 7.0;
|
||||||
let by = cp.y - sq - 13.0;
|
let by = cp.y - sq - 13.0;
|
||||||
// Lock body (filled).
|
// Lock body (filled).
|
||||||
|
|
@ -1058,12 +1005,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Object Snap Tracking ─────────────────────────────────────────────
|
// ── Object Snap Tracking ─────────────────────────────────────────────
|
||||||
let track_color = Color {
|
let track_color = theme.extended_palette().primary.base.color.scale_alpha(0.7);
|
||||||
r: 0.15,
|
|
||||||
g: 0.85,
|
|
||||||
b: 0.95,
|
|
||||||
a: 0.7,
|
|
||||||
};
|
|
||||||
// The alignment line the cursor is currently locked to — drawn at its
|
// The alignment line the cursor is currently locked to — drawn at its
|
||||||
// real angle from the acquired point through the lock and a little
|
// real angle from the acquired point through the lock and a little
|
||||||
// beyond, dashed so it reads as a construction guide. This covers the
|
// beyond, dashed so it reads as a construction guide. This covers the
|
||||||
|
|
@ -2142,10 +2084,12 @@ struct DynInputCanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DynInputCanvas {
|
impl DynInputCanvas {
|
||||||
fn dotted() -> canvas::Stroke<'static> {
|
fn dotted(theme: &Theme) -> canvas::Stroke<'static> {
|
||||||
canvas::Stroke {
|
canvas::Stroke {
|
||||||
width: 1.0,
|
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 },
|
line_dash: canvas::LineDash { segments: &[2.0, 3.0], offset: 0 },
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
@ -2160,13 +2104,19 @@ impl DynInputCanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a value box centred at `center`, clamped inside `bounds`.
|
/// 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 content = Self::box_content(b);
|
||||||
let w = (content.len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0;
|
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 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 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 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.fill(&rect, fill);
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&rect,
|
&rect,
|
||||||
|
|
@ -2177,7 +2127,7 @@ impl DynInputCanvas {
|
||||||
frame.fill_text(canvas::Text {
|
frame.fill_text(canvas::Text {
|
||||||
content,
|
content,
|
||||||
position: Point { x: x + DYN_PAD, y: y + DYN_PAD },
|
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),
|
size: iced::Pixels(DYN_FONT),
|
||||||
// Force Advanced shaping: the default `Auto` uses Basic shaping for
|
// Force Advanced shaping: the default `Auto` uses Basic shaping for
|
||||||
// ASCII-only strings, which the web (wgpu/webgl) backend fails to
|
// 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 {
|
if b.active {
|
||||||
(
|
(
|
||||||
Color { r: 0.12, g: 0.18, b: 0.30, a: 0.95 },
|
palette.primary.weak.color,
|
||||||
Color { r: 0.45, g: 0.70, b: 1.0, a: 1.0 },
|
palette.primary.base.color,
|
||||||
|
palette.primary.weak.text,
|
||||||
)
|
)
|
||||||
} else if b.locked {
|
} else if b.locked {
|
||||||
(
|
(
|
||||||
Color { r: 0.05, g: 0.05, b: 0.12, a: 0.9 },
|
palette.warning.weak.color,
|
||||||
Color { r: 0.95, g: 0.75, b: 0.30, a: 0.9 },
|
palette.warning.base.color,
|
||||||
|
palette.warning.weak.text,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
Color { r: 0.05, g: 0.05, b: 0.12, a: 0.9 },
|
palette.background.weak.color,
|
||||||
Color { r: 0.35, g: 0.55, b: 0.90, a: 0.9 },
|
palette.background.neutral.color,
|
||||||
|
palette.background.weak.text,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prompt pill at `pos`.
|
/// 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() {
|
if self.prompt.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let palette = theme.extended_palette();
|
||||||
let pw = (self.prompt.len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0;
|
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 });
|
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(
|
frame.stroke(
|
||||||
&rect,
|
&rect,
|
||||||
canvas::Stroke::default()
|
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),
|
.with_width(1.0),
|
||||||
);
|
);
|
||||||
frame.fill_text(canvas::Text {
|
frame.fill_text(canvas::Text {
|
||||||
content: self.prompt.clone(),
|
content: self.prompt.clone(),
|
||||||
position: Point { x: pos.x + DYN_PAD, y: pos.y + DYN_PAD },
|
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),
|
size: iced::Pixels(DYN_FONT),
|
||||||
shaping: iced::advanced::text::Shaping::Advanced,
|
shaping: iced::advanced::text::Shaping::Advanced,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -2233,7 +2188,13 @@ impl DynInputCanvas {
|
||||||
|
|
||||||
/// Guided layout: draw the guide geometry anchored at `base`, then place
|
/// Guided layout: draw the guide geometry anchored at `base`, then place
|
||||||
/// each box according to its role.
|
/// 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 cursor_raw = self.cursor_screen;
|
||||||
let (vx, vy) = (cursor_raw.x - base.x, cursor_raw.y - base.y);
|
let (vx, vy) = (cursor_raw.x - base.x, cursor_raw.y - base.y);
|
||||||
let raw_len = (vx * vx + vy * vy).sqrt().max(1.0);
|
let raw_len = (vx * vx + vy * vy).sqrt().max(1.0);
|
||||||
|
|
@ -2308,7 +2269,7 @@ impl DynInputCanvas {
|
||||||
y: base.y + a_ref.sin() * len,
|
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 arc = canvas::Path::new(|p| {
|
||||||
let steps = 48;
|
let steps = 48;
|
||||||
for k in 0..=steps {
|
for k in 0..=steps {
|
||||||
|
|
@ -2324,14 +2285,14 @@ impl DynInputCanvas {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
frame.stroke(&arc, Self::dotted());
|
frame.stroke(&arc, Self::dotted(theme));
|
||||||
}
|
}
|
||||||
DynGuide::Radius => {
|
DynGuide::Radius => {
|
||||||
let line = canvas::Path::new(|p| {
|
let line = canvas::Path::new(|p| {
|
||||||
p.move_to(base);
|
p.move_to(base);
|
||||||
p.line_to(cursor);
|
p.line_to(cursor);
|
||||||
});
|
});
|
||||||
frame.stroke(&line, Self::dotted());
|
frame.stroke(&line, Self::dotted(theme));
|
||||||
}
|
}
|
||||||
DynGuide::Perp => {
|
DynGuide::Perp => {
|
||||||
if let Some((end, _, _)) = perp_info {
|
if let Some((end, _, _)) = perp_info {
|
||||||
|
|
@ -2340,7 +2301,7 @@ impl DynInputCanvas {
|
||||||
p.move_to(base);
|
p.move_to(base);
|
||||||
p.line_to(end);
|
p.line_to(end);
|
||||||
});
|
});
|
||||||
frame.stroke(&line, Self::dotted());
|
frame.stroke(&line, Self::dotted(theme));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynGuide::PerpDim => {
|
DynGuide::PerpDim => {
|
||||||
|
|
@ -2351,14 +2312,14 @@ impl DynInputCanvas {
|
||||||
p.move_to(ob);
|
p.move_to(ob);
|
||||||
p.line_to(oe);
|
p.line_to(oe);
|
||||||
});
|
});
|
||||||
frame.stroke(&dim, Self::dotted());
|
frame.stroke(&dim, Self::dotted(theme));
|
||||||
let ext = canvas::Path::new(|p| {
|
let ext = canvas::Path::new(|p| {
|
||||||
p.move_to(base);
|
p.move_to(base);
|
||||||
p.line_to(ob);
|
p.line_to(ob);
|
||||||
p.move_to(end);
|
p.move_to(end);
|
||||||
p.line_to(oe);
|
p.line_to(oe);
|
||||||
});
|
});
|
||||||
frame.stroke(&ext, Self::dotted());
|
frame.stroke(&ext, Self::dotted(theme));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynGuide::AxisDelta | DynGuide::RectSides => {
|
DynGuide::AxisDelta | DynGuide::RectSides => {
|
||||||
|
|
@ -2368,7 +2329,7 @@ impl DynInputCanvas {
|
||||||
p.line_to(corner);
|
p.line_to(corner);
|
||||||
p.line_to(cursor);
|
p.line_to(cursor);
|
||||||
});
|
});
|
||||||
frame.stroke(&legs, Self::dotted());
|
frame.stroke(&legs, Self::dotted(theme));
|
||||||
if self.guide == DynGuide::RectSides {
|
if self.guide == DynGuide::RectSides {
|
||||||
// Close the rectangle so both side pairs read as a box.
|
// Close the rectangle so both side pairs read as a box.
|
||||||
let rest = canvas::Path::new(|p| {
|
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(Point { x: base.x, y: cursor.y });
|
||||||
p.line_to(cursor);
|
p.line_to(cursor);
|
||||||
});
|
});
|
||||||
frame.stroke(&rest, Self::dotted());
|
frame.stroke(&rest, Self::dotted(theme));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynGuide::None => {}
|
DynGuide::None => {}
|
||||||
|
|
@ -2423,12 +2384,12 @@ impl DynInputCanvas {
|
||||||
y: base.y + dy * len * 0.5 + ny * 16.0,
|
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).
|
/// 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
|
let texts: Vec<String> = self
|
||||||
.boxes
|
.boxes
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2466,7 +2427,7 @@ impl DynInputCanvas {
|
||||||
py = (by - pad - DYN_BOX_H).max(0.0);
|
py = (by - pad - DYN_BOX_H).max(0.0);
|
||||||
}
|
}
|
||||||
if has_prompt {
|
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;
|
let mut x = bx;
|
||||||
|
|
@ -2474,7 +2435,7 @@ impl DynInputCanvas {
|
||||||
let w = widths[i];
|
let w = widths[i];
|
||||||
let rect =
|
let rect =
|
||||||
canvas::Path::rectangle(Point { x, y: by }, Size { width: w, height: DYN_BOX_H });
|
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.fill(&rect, fill);
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&rect,
|
&rect,
|
||||||
|
|
@ -2485,7 +2446,7 @@ impl DynInputCanvas {
|
||||||
frame.fill_text(canvas::Text {
|
frame.fill_text(canvas::Text {
|
||||||
content: texts[i].clone(),
|
content: texts[i].clone(),
|
||||||
position: Point { x: x + DYN_PAD, y: by + DYN_PAD },
|
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),
|
size: iced::Pixels(DYN_FONT),
|
||||||
shaping: iced::advanced::text::Shaping::Advanced,
|
shaping: iced::advanced::text::Shaping::Advanced,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -2511,7 +2472,7 @@ impl canvas::Program<Message> for DynInputCanvas {
|
||||||
&self,
|
&self,
|
||||||
_state: &(),
|
_state: &(),
|
||||||
renderer: &iced::Renderer,
|
renderer: &iced::Renderer,
|
||||||
_theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: iced::Rectangle,
|
bounds: iced::Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) -> Vec<canvas::Geometry> {
|
||||||
|
|
@ -2529,15 +2490,15 @@ impl canvas::Program<Message> for DynInputCanvas {
|
||||||
if py + DYN_BOX_H > bounds.height {
|
if py + DYN_BOX_H > bounds.height {
|
||||||
py = (self.cursor_screen.y - DYN_BOX_H - 4.0).max(0.0);
|
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()];
|
return vec![frame.into_geometry()];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guided layouts need the anchor; without it fall back to a cursor row.
|
// Guided layouts need the anchor; without it fall back to a cursor row.
|
||||||
match (self.guide, self.base_screen) {
|
match (self.guide, self.base_screen) {
|
||||||
(DynGuide::None, _) | (_, None) => self.draw_row(&mut frame, bounds),
|
(DynGuide::None, _) | (_, None) => self.draw_row(&mut frame, bounds, theme),
|
||||||
(_, Some(base)) => self.draw_guided(&mut frame, bounds, base),
|
(_, Some(base)) => self.draw_guided(&mut frame, bounds, base, theme),
|
||||||
}
|
}
|
||||||
vec![frame.into_geometry()]
|
vec![frame.into_geometry()]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
//! adds that object to the current selection. Clicking outside dismisses it.
|
//! adds that object to the current selection. Clicking outside dismisses it.
|
||||||
|
|
||||||
use iced::widget::{button, column, container, mouse_area, opaque, text};
|
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;
|
use crate::app::Message;
|
||||||
|
|
||||||
|
|
@ -19,15 +19,7 @@ pub fn cycle_popup_overlay(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(150.0));
|
.width(Length::Fixed(150.0));
|
||||||
|
|
||||||
let positioned = crate::ui::pin_at(anchor, opaque(panel));
|
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> {
|
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)
|
let btn = button(content)
|
||||||
.on_press(Message::CycleSelect(handle))
|
.on_press(Message::CycleSelect(handle))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10]);
|
.padding([4, 10]);
|
||||||
// Highlight the underlying object while the cursor is over this row.
|
// 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))
|
.on_exit(Message::CycleHoverExit(handle))
|
||||||
.into()
|
.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.
|
//! Isolate / Hide / End Isolation status menu.
|
||||||
|
|
||||||
use iced::widget::{button, row, text};
|
use iced::widget::{button, row, text};
|
||||||
use iced::{Background, Color, Element, Fill, Theme};
|
use iced::{Element, Fill};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::ui::statusbar::status_menu::Entry;
|
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> {
|
fn action_row(label: &'static str, enabled: bool, msg: Message) -> Element<'static, Message> {
|
||||||
let lbl = text(label)
|
let lbl = text(label).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if enabled { LABEL_ON } else { LABEL_OFF });
|
|
||||||
let content = row![lbl].align_y(iced::Center);
|
let content = row![lbl].align_y(iced::Center);
|
||||||
|
|
||||||
let mut btn = button(content)
|
let mut btn = button(content)
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match (enabled, status) {
|
|
||||||
(true, button::Status::Hovered) => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 12]);
|
.padding([4, 12]);
|
||||||
if enabled {
|
if enabled {
|
||||||
|
|
@ -61,24 +53,3 @@ fn action_row(label: &'static str, enabled: bool, msg: Message) -> Element<'stat
|
||||||
}
|
}
|
||||||
btn.into()
|
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.
|
//! Polar-tracking angle status menu.
|
||||||
|
|
||||||
use iced::widget::{button, container, row, text, text_input};
|
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::app::Message;
|
||||||
use crate::ui::statusbar::status_menu::Entry;
|
use crate::ui::statusbar::status_menu::Entry;
|
||||||
|
|
@ -41,7 +41,7 @@ pub fn menu_entries<'a>(
|
||||||
let custom_row = container(
|
let custom_row = container(
|
||||||
row![
|
row![
|
||||||
custom_field,
|
custom_field,
|
||||||
text("°").size(11).color(LABEL_OFF),
|
text("°").size(11),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -52,51 +52,16 @@ pub fn menu_entries<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn angle_row<'a>(deg: f32, active: bool) -> Element<'a, Message> {
|
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))
|
let lbl = text(angle_label(deg)).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
|
||||||
|
|
||||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||||
|
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(Message::SetPolarAngle(deg))
|
.on_press(Message::SetPolarAngle(deg))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.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.
|
//! Annotation / viewport scale status menu.
|
||||||
|
|
||||||
use iced::widget::{button, row, text};
|
use iced::widget::{button, row, text};
|
||||||
use iced::{Background, Color, Element, Fill, Theme};
|
use iced::{Element, Fill};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::ui::statusbar::status_menu::Entry;
|
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> {
|
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)
|
let lbl = text(label).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
|
||||||
|
|
||||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||||
|
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_row() -> Element<'static, Message> {
|
fn manage_row() -> Element<'static, Message> {
|
||||||
button(text("Manage...").size(11).color(CHECK_COLOR))
|
button(text("Manage...").size(11))
|
||||||
.on_press(Message::ScaleManagerOpen)
|
.on_press(Message::ScaleManagerOpen)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::primary)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([5, 10])
|
.padding([5, 10])
|
||||||
.into()
|
.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 rustc_hash::FxHashSet as HashSet;
|
||||||
|
|
||||||
use iced::widget::{button, container, row, text};
|
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::app::Message;
|
||||||
use crate::ui::statusbar::status_menu::Entry;
|
use crate::ui::statusbar::status_menu::Entry;
|
||||||
|
|
@ -37,8 +37,10 @@ pub fn menu_entries(
|
||||||
.padding([4u16, 8]);
|
.padding([4u16, 8]);
|
||||||
|
|
||||||
let divider = container(iced::widget::Space::new().height(1))
|
let divider = container(iced::widget::Space::new().height(1))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(DIVIDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -62,99 +64,41 @@ pub fn menu_entries(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_row(name: String, included: bool) -> Element<'static, Message> {
|
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())
|
let lbl = text(name.clone()).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if included { LABEL_ON } else { LABEL_OFF });
|
|
||||||
|
|
||||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||||
|
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(Message::ToggleSelectionFilterType(name))
|
.on_press(Message::ToggleSelectionFilterType(name))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_row() -> Element<'static, Message> {
|
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])
|
.padding([4, 10])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
||||||
let color = if enabled {
|
let b = button(text(label).size(10));
|
||||||
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 = if enabled { b.on_press(msg) } else { b };
|
let b = if enabled { b.on_press(msg) } else { b };
|
||||||
b.style(|_: &Theme, status| button::Style {
|
b.style(button::secondary)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.into()
|
.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.
|
//! OpenCADStudio-style OSNAP status menu.
|
||||||
|
|
||||||
use iced::widget::{button, container, row, text};
|
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::app::Message;
|
||||||
use crate::snap::{SnapType, Snapper, ALL_SNAP_MODES};
|
use crate::snap::{SnapType, Snapper, ALL_SNAP_MODES};
|
||||||
|
|
@ -20,8 +20,10 @@ pub fn menu_entries<'a>(snapper: &'a Snapper) -> Vec<Entry<'a>> {
|
||||||
|
|
||||||
// Divider
|
// Divider
|
||||||
let divider = container(iced::widget::Space::new().height(1))
|
let divider = container(iced::widget::Space::new().height(1))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(DIVIDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -41,21 +43,18 @@ pub fn menu_entries<'a>(snapper: &'a Snapper) -> Vec<Entry<'a>> {
|
||||||
// ── Individual snap row ───────────────────────────────────────────────────
|
// ── Individual snap row ───────────────────────────────────────────────────
|
||||||
|
|
||||||
fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a, Message> {
|
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,
|
// SVG marker (not a Unicode glyph) so the symbols render on the web build,
|
||||||
// whose bundled font lacks them and showed tofu boxes. (#138)
|
// 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),
|
crate::ui::icons::osnap(snap_type),
|
||||||
13.0,
|
13.0,
|
||||||
ICON_COLOR,
|
|
||||||
))
|
))
|
||||||
.width(Length::Fixed(16.0))
|
.width(Length::Fixed(16.0))
|
||||||
.align_x(iced::Center);
|
.align_x(iced::Center);
|
||||||
|
|
||||||
let label_el = text(label)
|
let label_el = text(label).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
|
||||||
|
|
||||||
let content = row![checkmark, icon_el, label_el]
|
let content = row![checkmark, icon_el, label_el]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
|
|
@ -63,98 +62,16 @@ fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a
|
||||||
|
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(Message::ToggleSnap(snap_type))
|
.on_press(Message::ToggleSnap(snap_type))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> {
|
||||||
let b = button(text(label).size(10).color(if enabled {
|
let b = button(text(label).size(10));
|
||||||
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 = if enabled { b.on_press(msg) } else { b };
|
let b = if enabled { b.on_press(msg) } else { b };
|
||||||
b.style(|_: &Theme, status| button::Style {
|
b.style(button::secondary)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.into()
|
.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.
|
//! Drawing-units status menu.
|
||||||
|
|
||||||
use iced::widget::{button, row, text};
|
use iced::widget::{button, row, text};
|
||||||
use iced::{Background, Color, Element, Fill, Theme};
|
use iced::{Element, Fill};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::ui::statusbar::status_menu::Entry;
|
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> {
|
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)
|
let lbl = text(label).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if active { LABEL_ON } else { LABEL_OFF });
|
|
||||||
|
|
||||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||||
|
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.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> {
|
pub fn view(&self) -> Element<'_, Message> {
|
||||||
// ── Header ──────────────────────────────────────────────────────────
|
// ── Header ──────────────────────────────────────────────────────────
|
||||||
let header = container(text("Properties").size(12).color(Color::WHITE))
|
let header = container(text("Properties").size(12))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(HEADER_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -218,7 +220,10 @@ impl PropertiesPanel {
|
||||||
|
|
||||||
// ── Title bar (entity type / "No Selection") ─────────────────────
|
// ── Title bar (entity type / "No Selection") ─────────────────────
|
||||||
let title_content: Element<'_, Message> = if self.selection_groups.is_empty() {
|
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 {
|
} else {
|
||||||
combo_box(
|
combo_box(
|
||||||
&self.selection_group_combo,
|
&self.selection_group_combo,
|
||||||
|
|
@ -235,14 +240,17 @@ impl PropertiesPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
let title_bar = container(title_content)
|
let title_bar = container(title_content)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(SECTION_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weakest.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([4, 10]);
|
.padding([4, 10]);
|
||||||
|
|
@ -252,7 +260,7 @@ impl PropertiesPanel {
|
||||||
container(
|
container(
|
||||||
text("Select an object to view properties")
|
text("Select an object to view properties")
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(HINT_COLOR),
|
.style(hint_text_style),
|
||||||
)
|
)
|
||||||
.padding([10, 10])
|
.padding([10, 10])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -265,14 +273,17 @@ impl PropertiesPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
container(column![header, title_bar, content])
|
container(column![header, title_bar, content])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(250)
|
.width(250)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
|
@ -286,15 +297,22 @@ impl PropertiesPanel {
|
||||||
if self.sections.is_empty() {
|
if self.sections.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let title = container(text(crate::ui::text_util::elide(&self.title, 34)).size(FONT_SZ).color(SECTION_LABEL))
|
let title = container(
|
||||||
.style(|_: &Theme| container::Style {
|
text(crate::ui::text_util::elide(&self.title, 34))
|
||||||
background: Some(Background::Color(SECTION_BG)),
|
.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 {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([4, 10]);
|
.padding([4, 10]);
|
||||||
|
|
@ -306,14 +324,17 @@ impl PropertiesPanel {
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
container(col)
|
container(col)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(230)
|
.width(230)
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -324,15 +345,18 @@ impl PropertiesPanel {
|
||||||
|
|
||||||
fn render_section<'a>(&'a self, section: &'a PropSection) -> Element<'a, Message> {
|
fn render_section<'a>(&'a self, section: &'a PropSection) -> Element<'a, Message> {
|
||||||
// Section header
|
// Section header
|
||||||
let hdr = container(text(§ion.title).size(10).color(Color::WHITE))
|
let hdr = container(text(§ion.title).size(10))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(SECTION_HDR_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([3, 8]);
|
.padding([3, 8]);
|
||||||
|
|
@ -508,31 +532,25 @@ impl PropertiesPanel {
|
||||||
fn render_color_varies_row<'a>(&'a self, label: &'a str) -> Element<'a, Message> {
|
fn render_color_varies_row<'a>(&'a self, label: &'a str) -> Element<'a, Message> {
|
||||||
let color_btn = button(
|
let color_btn = button(
|
||||||
row![
|
row![
|
||||||
container(text("?").size(10).color(VALUE_COLOR))
|
container(text("?").size(10))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(Color {
|
let palette = theme.extended_palette();
|
||||||
r: 0.32,
|
container::Style {
|
||||||
g: 0.32,
|
background: Some(Background::Color(palette.background.strong.color)),
|
||||||
b: 0.32,
|
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: Color {
|
color: palette.background.neutral.color,
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.5
|
|
||||||
},
|
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into()
|
radius: 2.0.into()
|
||||||
},
|
},
|
||||||
|
text_color: Some(palette.background.strong.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(SWATCH_SZ)
|
.width(SWATCH_SZ)
|
||||||
.height(SWATCH_SZ)
|
.height(SWATCH_SZ)
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
text(VARIES_LABEL).size(FONT_SZ).color(VALUE_COLOR),
|
text(VARIES_LABEL).size(FONT_SZ),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -724,7 +742,7 @@ impl PropertiesPanel {
|
||||||
.on_input(move |v| Message::PropGeomInput { field, value: v })
|
.on_input(move |v| Message::PropGeomInput { field, value: v })
|
||||||
.on_submit(Message::PropGeomCommit(field))
|
.on_submit(Message::PropGeomCommit(field))
|
||||||
.size(FONT_SZ)
|
.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
|
// The wrapping container draws the border; keep the input flat
|
||||||
// so field + caret read as one control.
|
// so field + caret read as one control.
|
||||||
border: Border {
|
border: Border {
|
||||||
|
|
@ -732,33 +750,32 @@ impl PropertiesPanel {
|
||||||
width: 0.0,
|
width: 0.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..text_input_style(&Theme::Dark, status)
|
..text_input_style(theme, status)
|
||||||
})
|
})
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
let caret = button(
|
let caret = button(
|
||||||
container(crate::ui::icons::arrow_toggle(
|
container(if self.edit_choice_open {
|
||||||
self.edit_choice_open,
|
crate::ui::icons::themed_arrow_up(FONT_SZ)
|
||||||
FONT_SZ,
|
} else {
|
||||||
VALUE_COLOR,
|
crate::ui::icons::themed_arrow_down(FONT_SZ)
|
||||||
))
|
})
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::PropEditChoiceToggle)
|
.on_press(Message::PropEditChoiceToggle)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered | button::Status::Pressed => Color {
|
let pair = match status {
|
||||||
r: 0.28,
|
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||||
g: 0.28,
|
_ => palette.background.base,
|
||||||
b: 0.28,
|
};
|
||||||
a: 1.0,
|
button::Style {
|
||||||
},
|
background: Some(Background::Color(pair.color)),
|
||||||
_ => VALUE_BG,
|
text_color: pair.text,
|
||||||
})),
|
|
||||||
text_color: VALUE_COLOR,
|
|
||||||
border: Border::default(),
|
border: Border::default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
top: 0.0,
|
top: 0.0,
|
||||||
|
|
@ -768,14 +785,17 @@ impl PropertiesPanel {
|
||||||
})
|
})
|
||||||
.height(Length::Fixed(ROW_H - 6.0));
|
.height(Length::Fixed(ROW_H - 6.0));
|
||||||
let head = container(row![input, caret].align_y(iced::Center))
|
let head = container(row![input, caret].align_y(iced::Center))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(VALUE_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
|
@ -794,39 +814,15 @@ impl PropertiesPanel {
|
||||||
}
|
}
|
||||||
let value = opt.clone();
|
let value = opt.clone();
|
||||||
list = list.push(
|
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 })
|
.on_press(Message::PropGeomChoiceChanged { field, value })
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([2, 6])
|
.padding([2, 6])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let popup = container(scrollable(list).height(Length::Shrink))
|
let popup = container(scrollable(list).height(Length::Shrink))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.width(200)
|
.width(200)
|
||||||
.max_height(220.0);
|
.max_height(220.0);
|
||||||
|
|
@ -940,13 +936,13 @@ pub fn color_picker_dropdown<'a>(
|
||||||
r.push(
|
r.push(
|
||||||
button(text("").width(18).height(18))
|
button(text("").width(18).height(18))
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(bg)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: if matches!(status, button::Status::Hovered) {
|
color: if matches!(status, button::Status::Hovered) {
|
||||||
Color::WHITE
|
theme.extended_palette().primary.base.color
|
||||||
} else {
|
} else {
|
||||||
Color::BLACK
|
theme.extended_palette().background.neutral.color
|
||||||
},
|
},
|
||||||
width: if matches!(status, button::Status::Hovered) {
|
width: if matches!(status, button::Status::Hovered) {
|
||||||
1.5
|
1.5
|
||||||
|
|
@ -965,20 +961,20 @@ pub fn color_picker_dropdown<'a>(
|
||||||
// "More Colors…" toggle button
|
// "More Colors…" toggle button
|
||||||
let more_btn = button(
|
let more_btn = button(
|
||||||
row![
|
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…" })
|
text(if palette_open { "Less" } else { "More Colors…" })
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(HINT_COLOR),
|
.style(hint_text_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(palette_toggle_msg)
|
.on_press(palette_toggle_msg)
|
||||||
.style(|_: &Theme, _| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(PICKER_BG)),
|
|
||||||
text_color: HINT_COLOR,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([2, 6])
|
.padding([2, 6])
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
|
@ -989,14 +985,17 @@ pub fn color_picker_dropdown<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut col = column![container(inner)
|
let mut col = column![container(inner)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PICKER_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into()
|
radius: 0.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([6, 8])
|
.padding([6, 8])
|
||||||
.width(Length::Fill)]
|
.width(Length::Fill)]
|
||||||
|
|
@ -1019,18 +1018,13 @@ pub fn color_picker_dropdown<'a>(
|
||||||
r = r.push(
|
r = r.push(
|
||||||
button(text("").width(12).height(12))
|
button(text("").width(12).height(12))
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(bg)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: if matches!(status, button::Status::Hovered) {
|
color: if matches!(status, button::Status::Hovered) {
|
||||||
Color::WHITE
|
theme.extended_palette().primary.base.color
|
||||||
} else {
|
} else {
|
||||||
Color {
|
theme.extended_palette().background.neutral.color
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.4,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
width: if matches!(status, button::Status::Hovered) {
|
width: if matches!(status, button::Status::Hovered) {
|
||||||
1.5
|
1.5
|
||||||
|
|
@ -1049,14 +1043,17 @@ pub fn color_picker_dropdown<'a>(
|
||||||
}
|
}
|
||||||
col = col.push(
|
col = col.push(
|
||||||
container(scrollable(rows).height(160))
|
container(scrollable(rows).height(160))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PICKER_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
|
|
@ -1071,22 +1068,23 @@ pub fn color_picker_dropdown<'a>(
|
||||||
/// A boolean toggle button row (for "Invisible" etc.).
|
/// A boolean toggle button row (for "Invisible" etc.).
|
||||||
fn render_stepper_row<'a>(label: &'a str, display: &'a str) -> Element<'a, Message> {
|
fn render_stepper_row<'a>(label: &'a str, display: &'a str) -> Element<'a, Message> {
|
||||||
let arrow = |glyph: &'static str, delta: i8| {
|
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))
|
.on_press(Message::PropVertexStep(delta))
|
||||||
.padding([0, 6])
|
.padding([0, 6])
|
||||||
.style(|_: &Theme, status| {
|
.style(|theme: &Theme, status| {
|
||||||
let bg = match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
|
let pair = match status {
|
||||||
_ => VALUE_BG,
|
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||||
|
_ => palette.background.base,
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
text_color: VALUE_COLOR,
|
text_color: pair.text,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -1095,7 +1093,6 @@ fn render_stepper_row<'a>(label: &'a str, display: &'a str) -> Element<'a, Messa
|
||||||
arrow("◀", -1),
|
arrow("◀", -1),
|
||||||
text(display)
|
text(display)
|
||||||
.size(FONT_SZ)
|
.size(FONT_SZ)
|
||||||
.color(VALUE_COLOR)
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_x(iced::Center),
|
.align_x(iced::Center),
|
||||||
arrow("▶", 1),
|
arrow("▶", 1),
|
||||||
|
|
@ -1111,22 +1108,29 @@ fn render_bool_row<'a>(label: &'a str, field: &'static str, value: bool) -> Elem
|
||||||
button(
|
button(
|
||||||
text(btn_label)
|
text(btn_label)
|
||||||
.size(FONT_SZ)
|
.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))
|
.on_press(Message::PropBoolToggle(field))
|
||||||
.style(move |_: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
let bg = match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
|
let pair = match status {
|
||||||
_ => VALUE_BG,
|
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||||
|
_ => palette.background.base,
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
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()
|
..Default::default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -1211,10 +1215,14 @@ fn render_group_row(
|
||||||
let label_btn = button(
|
let label_btn = button(
|
||||||
container(
|
container(
|
||||||
row![
|
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))
|
text(crate::ui::text_util::elide(base, 16))
|
||||||
.size(FONT_SZ)
|
.size(FONT_SZ)
|
||||||
.color(LABEL_COLOR),
|
.style(muted_text_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -1223,20 +1231,7 @@ fn render_group_row(
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::PropGroupToggle(key))
|
.on_press(Message::PropGroupToggle(key))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
top: 0.0,
|
top: 0.0,
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
|
|
@ -1246,8 +1241,10 @@ fn render_group_row(
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fixed(ROW_H));
|
.height(Length::Fixed(ROW_H));
|
||||||
let label_col = container(label_btn)
|
let label_col = container(label_btn)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(LABEL_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::FillPortion(5))
|
.width(Length::FillPortion(5))
|
||||||
|
|
@ -1262,8 +1259,10 @@ fn render_group_row(
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
let value_col = container(value_field)
|
let value_col = container(value_field)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(VALUE_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::FillPortion(6))
|
.width(Length::FillPortion(6))
|
||||||
|
|
@ -1278,9 +1277,9 @@ fn render_group_row(
|
||||||
|
|
||||||
container(row![label_col, value_col])
|
container(row![label_col, value_col])
|
||||||
.height(Length::Fixed(ROW_H))
|
.height(Length::Fixed(ROW_H))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: theme.extended_palette().background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
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.
|
/// Build a label | widget property row.
|
||||||
fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
|
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))
|
let label_col = container(
|
||||||
.style(|_: &Theme| container::Style {
|
text(crate::ui::text_util::elide(label, 18))
|
||||||
background: Some(Background::Color(LABEL_BG)),
|
.size(FONT_SZ)
|
||||||
|
.style(muted_text_style),
|
||||||
|
)
|
||||||
|
.style(|theme: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::FillPortion(5))
|
.width(Length::FillPortion(5))
|
||||||
|
|
@ -1320,8 +1325,10 @@ fn prop_row_widget<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<
|
||||||
right: 6.0,
|
right: 6.0,
|
||||||
});
|
});
|
||||||
let value_col = container(widget)
|
let value_col = container(widget)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(VALUE_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::FillPortion(6))
|
.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])
|
container(row![label_col, value_col])
|
||||||
.height(Length::Fixed(ROW_H))
|
.height(Length::Fixed(ROW_H))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: theme.extended_palette().background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
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.
|
/// A plain text button used inside the color picker for ByLayer / ByBlock.
|
||||||
fn picker_text_btn(label: &str, msg: Message) -> Element<'_, Message> {
|
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)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, _| button::Style {
|
.style(button::secondary)
|
||||||
background: Some(Background::Color(LABEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 2.0.into(),
|
|
||||||
},
|
|
||||||
text_color: VALUE_COLOR,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([2, 8])
|
.padding([2, 8])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -1419,49 +1417,41 @@ fn aci_label(idx: u8) -> &'static str {
|
||||||
|
|
||||||
// ── Widget style helpers ──────────────────────────────────────────────────
|
// ── Widget style helpers ──────────────────────────────────────────────────
|
||||||
|
|
||||||
fn combo_btn_style(_theme: &Theme, status: button::Status) -> button::Style {
|
fn combo_btn_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||||
let bg = match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered | button::Status::Pressed => HOVER_BG,
|
let pair = match status {
|
||||||
_ => VALUE_BG,
|
button::Status::Hovered | button::Status::Pressed => palette.background.weak,
|
||||||
|
_ => palette.background.base,
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
text_color: VALUE_COLOR,
|
text_color: pair.text,
|
||||||
..Default::default()
|
..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 {
|
let border_color = match status {
|
||||||
text_input::Status::Focused { .. } => Color {
|
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||||
r: 0.3,
|
_ => palette.background.neutral.color,
|
||||||
g: 0.6,
|
|
||||||
b: 1.0,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => BORDER,
|
|
||||||
};
|
};
|
||||||
text_input::Style {
|
text_input::Style {
|
||||||
background: Background::Color(VALUE_BG),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: border_color,
|
color: border_color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
icon: Color::TRANSPARENT,
|
icon: Color::TRANSPARENT,
|
||||||
placeholder: HINT_COLOR,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: VALUE_COLOR,
|
value: palette.background.base.text,
|
||||||
selection: Color {
|
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||||
r: 0.2,
|
|
||||||
g: 0.4,
|
|
||||||
b: 0.8,
|
|
||||||
a: 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
|
/// 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
|
/// 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.
|
/// 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 {
|
text_input::Style {
|
||||||
background: Background::Color(VALUE_BG),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: Color::TRANSPARENT,
|
color: Color::TRANSPARENT,
|
||||||
width: 0.0,
|
width: 0.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
icon: Color::TRANSPARENT,
|
icon: Color::TRANSPARENT,
|
||||||
placeholder: HINT_COLOR,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: VALUE_COLOR,
|
value: palette.background.base.text,
|
||||||
selection: Color {
|
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||||
r: 0.2,
|
|
||||||
g: 0.4,
|
|
||||||
b: 0.8,
|
|
||||||
a: 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 {
|
fn hint_text_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.19,
|
iced::widget::text::Style {
|
||||||
g: 0.19,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.48)),
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use iced::advanced::layout::{self, Layout};
|
||||||
use iced::advanced::widget::{self, Widget};
|
use iced::advanced::widget::{self, Widget};
|
||||||
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
||||||
use iced::{
|
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,
|
Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -113,8 +113,6 @@ pub struct CollapsePanels<'a> {
|
||||||
open: Option<String>,
|
open: Option<String>,
|
||||||
/// Fallback row height, used only when there are no panels to measure.
|
/// Fallback row height, used only when there are no panels to measure.
|
||||||
row_h: f32,
|
row_h: f32,
|
||||||
/// Colour of the 1px divider drawn between panels.
|
|
||||||
divider: Color,
|
|
||||||
/// Chosen degradation level per panel; set during layout.
|
/// Chosen degradation level per panel; set during layout.
|
||||||
levels: RefCell<Vec<u8>>,
|
levels: RefCell<Vec<u8>>,
|
||||||
/// If set, the measured row height is written here each layout (read when
|
/// 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> {
|
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();
|
let n = panels.len();
|
||||||
Self {
|
Self {
|
||||||
panels,
|
panels,
|
||||||
open,
|
open,
|
||||||
row_h,
|
row_h,
|
||||||
divider,
|
|
||||||
levels: RefCell::new(vec![FULL; n]),
|
levels: RefCell::new(vec![FULL; n]),
|
||||||
height_out: None,
|
height_out: None,
|
||||||
tight_out: None,
|
tight_out: None,
|
||||||
|
|
@ -480,7 +477,7 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||||
shadow: Shadow::default(),
|
shadow: Shadow::default(),
|
||||||
snap: true,
|
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 rustc_hash::FxHashMap as HashMap;
|
||||||
|
|
||||||
use acadrust::types::{Color as AcadColor, LineWeight};
|
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 iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
|
@ -419,58 +419,41 @@ impl Ribbon {
|
||||||
|
|
||||||
let is_active = i == self.active;
|
let is_active = i == self.active;
|
||||||
let is_contextual = module.id() == "layout";
|
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(
|
let btn = container(
|
||||||
button(text(module.title()).size(12))
|
button(text(module.title()).size(12))
|
||||||
.on_press(Message::RibbonSelectTab(i))
|
.on_press(Message::RibbonSelectTab(i))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (is_active, status) {
|
let palette = theme.extended_palette();
|
||||||
(true, _) => RIBBON_BG,
|
let accent = if is_contextual {
|
||||||
(false, button::Status::Hovered) => hover_bg,
|
palette.warning.base
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
text_color: if is_active {
|
|
||||||
Color::WHITE
|
|
||||||
} else {
|
} 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 {
|
border: Border {
|
||||||
color: if is_active {
|
color: if is_active {
|
||||||
accent
|
accent.color
|
||||||
} else {
|
} else {
|
||||||
Color::TRANSPARENT
|
Color::TRANSPARENT
|
||||||
},
|
},
|
||||||
|
|
@ -479,13 +462,18 @@ impl Ribbon {
|
||||||
},
|
},
|
||||||
shadow: iced::Shadow::default(),
|
shadow: iced::Shadow::default(),
|
||||||
snap: false,
|
snap: false,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([5, 14]),
|
.padding([5, 14]),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| container::Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: if is_active {
|
color: if is_active {
|
||||||
accent
|
if is_contextual {
|
||||||
|
theme.extended_palette().warning.base.color
|
||||||
|
} else {
|
||||||
|
theme.extended_palette().primary.base.color
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Color::TRANSPARENT
|
Color::TRANSPARENT
|
||||||
},
|
},
|
||||||
|
|
@ -514,9 +502,11 @@ impl Ribbon {
|
||||||
// persisted (see `Ribbon::set_collapse_mode`). It hides itself once the
|
// persisted (see `Ribbon::set_collapse_mode`). It hides itself once the
|
||||||
// tool row is tight, giving the cramped tab row its space back.
|
// 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 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()))
|
.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)
|
.height(24)
|
||||||
.padding([2, 8]);
|
.padding([2, 8]);
|
||||||
let mode_dd = PosReport::new(COLLAPSE_MODE_ID, mode_btn);
|
let mode_dd = PosReport::new(COLLAPSE_MODE_ID, mode_btn);
|
||||||
|
|
@ -534,8 +524,10 @@ impl Ribbon {
|
||||||
}
|
}
|
||||||
|
|
||||||
let tab_bar = container(tab_row)
|
let tab_bar = container(tab_row)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TOPBAR_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
|
|
@ -653,10 +645,12 @@ impl Ribbon {
|
||||||
self.active_lineweight,
|
self.active_lineweight,
|
||||||
&style_ctx,
|
&style_ctx,
|
||||||
))
|
))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(RIBBON_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_DARK,
|
color: theme.extended_palette().background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -666,7 +660,7 @@ impl Ribbon {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.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_height(self.tool_bar_h.clone())
|
||||||
.report_tight(self.collapse_tight.clone())
|
.report_tight(self.collapse_tight.clone())
|
||||||
.mode(self.collapse_mode)
|
.mode(self.collapse_mode)
|
||||||
|
|
@ -676,10 +670,12 @@ impl Ribbon {
|
||||||
};
|
};
|
||||||
|
|
||||||
let tool_bar = container(tool_area)
|
let tool_bar = container(tool_area)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(RIBBON_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_DARK,
|
color: theme.extended_palette().background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -712,19 +708,13 @@ impl Ribbon {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, label)| {
|
.map(|(idx, label)| {
|
||||||
let step = idx + 1;
|
let step = idx + 1;
|
||||||
button(text(label.clone()).size(11).color(LABEL_ON))
|
button(text(label.clone()).size(11))
|
||||||
.on_press(if is_undo {
|
.on_press(if is_undo {
|
||||||
Message::UndoMany(step)
|
Message::UndoMany(step)
|
||||||
} else {
|
} else {
|
||||||
Message::RedoMany(step)
|
Message::RedoMany(step)
|
||||||
})
|
})
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([5, 10])
|
.padding([5, 10])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -732,15 +722,7 @@ impl Ribbon {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(170.0));
|
.width(Length::Fixed(170.0));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, 170.0, win.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
|
let rows: Vec<Element<Message>> = CollapseMode::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&m| {
|
.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(
|
button(
|
||||||
row![
|
row![
|
||||||
container(mark).width(Length::Fixed(16.0)),
|
crate::ui::icons::themed_check_cell(m == current),
|
||||||
text(m.label()).size(11).color(LABEL_ON),
|
text(m.label()).size(11),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::SetRibbonCollapseMode(m))
|
.on_press(Message::SetRibbonCollapseMode(m))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([5, 10])
|
.padding([5, 10])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -783,15 +754,7 @@ impl Ribbon {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(W));
|
.width(Length::Fixed(W));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, W, win.0);
|
let (align_right, h_pad, top) = self.dd_anchor(open_id, W, win.0);
|
||||||
|
|
@ -852,28 +815,25 @@ impl Ribbon {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(cmd, label, item_icon)| {
|
.map(|(cmd, label, item_icon)| {
|
||||||
let is_current = *cmd == last_cmd;
|
let is_current = *cmd == last_cmd;
|
||||||
let checkmark: Element<'_, Message> = container(if is_current {
|
let checkmark: Element<'_, Message> =
|
||||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
crate::ui::icons::themed_check_cell(is_current);
|
||||||
} else {
|
let icon_el: Element<Message> =
|
||||||
iced::widget::Space::new().width(0).into()
|
container(make_icon(*item_icon, 20.0))
|
||||||
})
|
|
||||||
.width(Length::Fixed(14.0))
|
|
||||||
.into();
|
|
||||||
let icon_el: Element<Message> = match *item_icon {
|
|
||||||
IconKind::Glyph(s) => text(s)
|
|
||||||
.size(13)
|
|
||||||
.color(ICON_COLOR)
|
|
||||||
.width(Length::Fixed(20.0))
|
.width(Length::Fixed(20.0))
|
||||||
.into(),
|
.into();
|
||||||
IconKind::Svg(bytes) => {
|
|
||||||
let handle = svg::Handle::from_memory(bytes);
|
|
||||||
svg(handle).width(20).height(20).into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let label_el =
|
let label_el =
|
||||||
text(*label)
|
text(*label)
|
||||||
.size(11)
|
.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(
|
button(
|
||||||
row![checkmark, icon_el, label_el]
|
row![checkmark, icon_el, label_el]
|
||||||
|
|
@ -884,13 +844,7 @@ impl Ribbon {
|
||||||
dropdown_id: dd_id,
|
dropdown_id: dd_id,
|
||||||
cmd: *cmd,
|
cmd: *cmd,
|
||||||
})
|
})
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -898,15 +852,7 @@ impl Ribbon {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(190.0));
|
.width(Length::Fixed(190.0));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, 190.0, win.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> {
|
let icon_btn = |bytes: &'static [u8], msg: Message| -> Element<'_, Message> {
|
||||||
button(crate::ui::icons::raw(bytes, 14.0))
|
button(crate::ui::icons::raw(bytes, 14.0))
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([2, 4])
|
.padding([2, 4])
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
@ -952,10 +892,10 @@ impl Ribbon {
|
||||||
let name = info.name.clone();
|
let name = info.name.clone();
|
||||||
|
|
||||||
let swatch = container(text(""))
|
let swatch = container(text(""))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(lc)),
|
background: Some(Background::Color(lc)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: SWATCH_BORDER,
|
color: theme.extended_palette().background.strong.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 1.0.into(),
|
radius: 1.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -976,29 +916,27 @@ impl Ribbon {
|
||||||
crate::ui::icons::layer_lock(ll),
|
crate::ui::icons::layer_lock(ll),
|
||||||
Message::LayerToggleLock(index),
|
Message::LayerToggleLock(index),
|
||||||
);
|
);
|
||||||
let checkmark: Element<'_, Message> = container(if is_active {
|
let checkmark: Element<'_, Message> =
|
||||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
crate::ui::icons::themed_check_cell(is_active);
|
||||||
} else {
|
|
||||||
iced::widget::Space::new().width(0).into()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(14.0))
|
|
||||||
.into();
|
|
||||||
let label =
|
let label =
|
||||||
text(&info.name)
|
text(&info.name)
|
||||||
.size(11)
|
.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
|
// The swatch + label area selects the layer as active; the
|
||||||
// icon buttons above handle their own toggles.
|
// icon buttons above handle their own toggles.
|
||||||
let select = button(row![swatch, label].spacing(5).align_y(iced::Center))
|
let select = button(row![swatch, label].spacing(5).align_y(iced::Center))
|
||||||
.on_press(Message::RibbonLayerChanged(name))
|
.on_press(Message::RibbonLayerChanged(name))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 4]);
|
.padding([4, 4]);
|
||||||
|
|
||||||
|
|
@ -1021,34 +959,7 @@ impl Ribbon {
|
||||||
let search = iced::widget::text_input("Search layers…", &self.layer_filter)
|
let search = iced::widget::text_input("Search layers…", &self.layer_filter)
|
||||||
.on_input(Message::RibbonLayerFilterChanged)
|
.on_input(Message::RibbonLayerFilterChanged)
|
||||||
.size(11)
|
.size(11)
|
||||||
.padding([4, 6])
|
.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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let panel = container(
|
let panel = container(
|
||||||
column![
|
column![
|
||||||
container(search).padding([4, 4]),
|
container(search).padding([4, 4]),
|
||||||
|
|
@ -1056,15 +967,7 @@ impl Ribbon {
|
||||||
]
|
]
|
||||||
.spacing(2),
|
.spacing(2),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(220.0));
|
.width(Length::Fixed(220.0));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(LAYER_COMBO_ID, 220.0, win.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 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
|
let mut rows: Vec<Element<Message>> = ctx
|
||||||
.names_for(style_key)
|
.names_for(style_key)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let is_sel = name.as_str() == active.as_str();
|
let is_sel = name.as_str() == active.as_str();
|
||||||
let n = name.clone();
|
let n = name.clone();
|
||||||
let checkmark: Element<Message> = container(if is_sel {
|
let checkmark: Element<Message> =
|
||||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
crate::ui::icons::themed_check_cell(is_sel);
|
||||||
} else {
|
|
||||||
iced::widget::Space::new().width(0).into()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(14.0))
|
|
||||||
.into();
|
|
||||||
button(
|
button(
|
||||||
row![
|
row![
|
||||||
checkmark,
|
checkmark,
|
||||||
text(name.clone()).size(11).color(if is_sel {
|
text(name.clone())
|
||||||
LABEL_ON
|
.size(11)
|
||||||
} else {
|
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||||
LABEL_OFF
|
color: (!is_sel).then_some(
|
||||||
}),
|
theme
|
||||||
|
.extended_palette()
|
||||||
|
.background
|
||||||
|
.base
|
||||||
|
.text
|
||||||
|
.scale_alpha(0.72),
|
||||||
|
),
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -1150,7 +1047,7 @@ impl Ribbon {
|
||||||
key: style_key,
|
key: style_key,
|
||||||
name: n,
|
name: n,
|
||||||
})
|
})
|
||||||
.style(row_style)
|
.style(popup_row_style)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -1159,9 +1056,9 @@ impl Ribbon {
|
||||||
|
|
||||||
if let Some(mgr) = manager_cmd {
|
if let Some(mgr) = manager_cmd {
|
||||||
rows.push(
|
rows.push(
|
||||||
button(text("Manage…").size(11).color(LABEL_ON))
|
button(text("Manage…").size(11))
|
||||||
.on_press(Message::Command(mgr.to_string()))
|
.on_press(Message::Command(mgr.to_string()))
|
||||||
.style(row_style)
|
.style(popup_row_style)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -1169,15 +1066,7 @@ impl Ribbon {
|
||||||
}
|
}
|
||||||
|
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(LARGE_W * 2.3));
|
.width(Length::Fixed(LARGE_W * 2.3));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(open_id, LARGE_W * 2.3, win.0);
|
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)
|
let panel = container(picker)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(200.0));
|
.width(Length::Fixed(200.0));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(PROP_COLOR_ID, 200.0, win.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()
|
.into_iter()
|
||||||
.map(|lt| {
|
.map(|lt| {
|
||||||
let is_cur = lt.name == *active_lt;
|
let is_cur = lt.name == *active_lt;
|
||||||
let check: Element<'_, Message> = container(if is_cur {
|
let check: Element<'_, Message> =
|
||||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
crate::ui::icons::themed_check_cell(is_cur);
|
||||||
} else {
|
|
||||||
iced::widget::Space::new().width(0).into()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(14.0))
|
|
||||||
.into();
|
|
||||||
let name_col = text(lt.name.clone())
|
let name_col = text(lt.name.clone())
|
||||||
.size(11)
|
.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));
|
.width(Length::Fixed(90.0));
|
||||||
let art_col = text(lt.art.clone()).size(9).color(Color {
|
let art_col = text(lt.art.clone()).size(9).style(muted_text_style);
|
||||||
r: 0.55,
|
|
||||||
g: 0.55,
|
|
||||||
b: 0.55,
|
|
||||||
a: 1.0,
|
|
||||||
});
|
|
||||||
let name = lt.name.clone();
|
let name = lt.name.clone();
|
||||||
button(
|
button(
|
||||||
row![check, name_col, art_col]
|
row![check, name_col, art_col]
|
||||||
|
|
@ -1267,13 +1147,7 @@ impl Ribbon {
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::RibbonLinetypeChanged(name))
|
.on_press(Message::RibbonLinetypeChanged(name))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -1281,15 +1155,7 @@ impl Ribbon {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let list = container(scrollable(column(rows)).height(Length::Fixed(200.0)))
|
let list = container(scrollable(column(rows)).height(Length::Fixed(200.0)))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(220.0));
|
.width(Length::Fixed(220.0));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(PROP_LINETYPE_ID, 220.0, win.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| {
|
.map(|item| {
|
||||||
let is_cur = item.0 == active_lw;
|
let is_cur = item.0 == active_lw;
|
||||||
let label = item.to_string();
|
let label = item.to_string();
|
||||||
let check: Element<'_, Message> = container(if is_cur {
|
let check: Element<'_, Message> =
|
||||||
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
crate::ui::icons::themed_check_cell(is_cur);
|
||||||
} else {
|
|
||||||
iced::widget::Space::new().width(0).into()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(14.0))
|
|
||||||
.into();
|
|
||||||
button(
|
button(
|
||||||
row![
|
row![
|
||||||
check,
|
check,
|
||||||
text(label)
|
text(label)
|
||||||
.size(11)
|
.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)
|
.spacing(5)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::RibbonLineweightChanged(item.0))
|
.on_press(Message::RibbonLineweightChanged(item.0))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(popup_row_style)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -1350,15 +1214,7 @@ impl Ribbon {
|
||||||
win: (f32, f32),
|
win: (f32, f32),
|
||||||
) -> Option<Element<'a, Message>> {
|
) -> Option<Element<'a, Message>> {
|
||||||
let panel = container(column(rows))
|
let panel = container(column(rows))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(popup_panel_style)
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: PANEL_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(width));
|
.width(Length::Fixed(width));
|
||||||
|
|
||||||
let (align_right, h_pad, top) = self.dd_anchor(dd_id, width, win.0);
|
let (align_right, h_pad, top) = self.dd_anchor(dd_id, width, win.0);
|
||||||
|
|
@ -1439,7 +1295,7 @@ fn render_group<'a>(
|
||||||
|
|
||||||
column![
|
column![
|
||||||
tools_el,
|
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)
|
.align_x(iced::Center)
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
|
|
@ -1526,8 +1382,8 @@ fn collapse_button<'a>(
|
||||||
column![
|
column![
|
||||||
icon,
|
icon,
|
||||||
row![
|
row![
|
||||||
text(title.to_string()).size(9).color(GROUP_LABEL),
|
text(title.to_string()).size(9).style(muted_text_style),
|
||||||
crate::ui::icons::arrow_down(8.0, GROUP_LABEL),
|
crate::ui::icons::themed_secondary_arrow_down(8.0),
|
||||||
]
|
]
|
||||||
.spacing(3)
|
.spacing(3)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -1536,22 +1392,7 @@ fn collapse_button<'a>(
|
||||||
.spacing(2),
|
.spacing(2),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonPanel(title.to_string()))
|
.on_press(Message::ToggleRibbonPanel(title.to_string()))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([3, 5])
|
.padding([3, 5])
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|
@ -1594,29 +1435,14 @@ fn collapse_button<'a>(
|
||||||
|
|
||||||
let opener = button(
|
let opener = button(
|
||||||
row![
|
row![
|
||||||
text(title.to_string()).size(9).color(GROUP_LABEL),
|
text(title.to_string()).size(9).style(muted_text_style),
|
||||||
crate::ui::icons::arrow_down(8.0, GROUP_LABEL),
|
crate::ui::icons::themed_secondary_arrow_down(8.0),
|
||||||
]
|
]
|
||||||
.spacing(3)
|
.spacing(3)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonPanel(title.to_string()))
|
.on_press(Message::ToggleRibbonPanel(title.to_string()))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([1, 4]);
|
.padding([1, 4]);
|
||||||
|
|
||||||
// The large face fills a fixed slot so a collapsed panel is shorter than a full
|
// 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
|
// Ribbon tooltips anchor to the right of their button so the cursor — which
|
||||||
// rests on the button itself — never covers the tip text. (#143)
|
// rests on the button itself — never covers the tip text. (#143)
|
||||||
use iced::widget::tooltip::Position as TipPos;
|
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 iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||||
|
|
||||||
use crate::app::Message;
|
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_LINETYPE_ID: &str = "PROP_LINETYPE";
|
||||||
pub(super) const PROP_LW_ID: &str = "PROP_LW";
|
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) ────────────────────
|
// ── Style context (passed from Ribbon to render_large) ────────────────────
|
||||||
|
|
||||||
pub(super) struct StyleContext {
|
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> {
|
pub(super) fn make_icon(icon: IconKind, size: f32) -> Element<'static, Message> {
|
||||||
match icon {
|
match icon {
|
||||||
IconKind::Glyph(s) => text(s).size(size * 0.7).color(Color::WHITE).into(),
|
IconKind::Glyph(s) => text(s).size(size * 0.7).into(),
|
||||||
IconKind::Svg(bytes) => {
|
IconKind::Svg(bytes) => icons::themed(bytes, size),
|
||||||
let handle = svg::Handle::from_memory(bytes);
|
|
||||||
svg(handle).width(size).height(size).into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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))
|
&& !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).
|
/// `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> {
|
pub(super) fn make_icon_dim(icon: IconKind, size: f32, dim: bool) -> Element<'static, Message> {
|
||||||
if !dim {
|
if !dim {
|
||||||
return make_icon(icon, size);
|
return make_icon(icon, size);
|
||||||
}
|
}
|
||||||
match icon {
|
match icon {
|
||||||
IconKind::Glyph(s) => text(s).size(size * 0.7).color(DIM_TOOL).into(),
|
IconKind::Glyph(s) => text(s)
|
||||||
IconKind::Svg(bytes) => icons::tinted(bytes, size, DIM_TOOL),
|
.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 ───────────────────────────────────────────────────────────
|
// ── 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 {
|
button::Style {
|
||||||
background: Some(Background::Color(match (is_active, status) {
|
background: is_active
|
||||||
(true, _) => TOOL_ACTIVE,
|
.then_some(Background::Color(pair.color))
|
||||||
(_, button::Status::Hovered) => TOOL_HOVER,
|
.or_else(|| matches!(status, button::Status::Hovered | button::Status::Pressed)
|
||||||
(_, button::Status::Pressed) => TOOL_ACTIVE,
|
.then_some(Background::Color(pair.color))),
|
||||||
_ => Color::TRANSPARENT,
|
text_color: pair.text,
|
||||||
})),
|
|
||||||
text_color: Color::WHITE,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
color: Color::TRANSPARENT,
|
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 combo_btn_style(
|
||||||
|
theme: &Theme,
|
||||||
pub(super) fn make_tip(tip: String) -> Element<'static, Message> {
|
is_open: bool,
|
||||||
text(tip).size(11).color(Color::WHITE).into()
|
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 {
|
container::Style {
|
||||||
background: Some(Background::Color(TIP_BG)),
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: COMBO_BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
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()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +314,7 @@ pub(super) fn render_small<'a>(
|
||||||
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
let tip_text = format!("{}\nCommand: {}", t.label, t.id);
|
||||||
let btn = button(make_icon_dim(t.icon, SMALL_ICON, dim))
|
let btn = button(make_icon_dim(t.icon, SMALL_ICON, dim))
|
||||||
.on_press(Message::RibbonToolClick { tool_id, event })
|
.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))
|
.width(Length::Fixed(SMALL_W))
|
||||||
.height(ROW_H)
|
.height(ROW_H)
|
||||||
.padding([4, 4]);
|
.padding([4, 4]);
|
||||||
|
|
@ -474,31 +380,22 @@ pub(super) fn render_small<'a>(
|
||||||
tool_id: last.to_string(),
|
tool_id: last.to_string(),
|
||||||
event: ModuleEvent::Command(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))
|
.width(Length::Fixed(SMALL_W))
|
||||||
.height(ROW_H)
|
.height(ROW_H)
|
||||||
.padding([4, 4]);
|
.padding([4, 4]);
|
||||||
|
|
||||||
let arr_tip = format!("{} options", cur_label);
|
let arr_tip = format!("{} options", cur_label);
|
||||||
let arr_btn = button(
|
let arr_btn = button(
|
||||||
container(icons::arrow_down(8.0, ARROW_COLOR))
|
container(icons::themed_arrow_down(8.0))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
tool_btn_style(theme, dd_open, 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()
|
|
||||||
})
|
})
|
||||||
.width(Length::Fixed(ARROW_W))
|
.width(Length::Fixed(ARROW_W))
|
||||||
.height(ROW_H)
|
.height(ROW_H)
|
||||||
|
|
@ -569,7 +466,9 @@ pub(super) fn render_large_dropdown<'a>(
|
||||||
let top_btn = button(
|
let top_btn = button(
|
||||||
column![
|
column![
|
||||||
make_icon_dim(cur_icon, LARGE_ICON, dim),
|
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)
|
.align_x(iced::Center)
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
|
|
@ -578,7 +477,7 @@ pub(super) fn render_large_dropdown<'a>(
|
||||||
tool_id: last.to_string(),
|
tool_id: last.to_string(),
|
||||||
event: ModuleEvent::Command(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))
|
.width(Length::Fixed(LARGE_W))
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
|
|
@ -589,24 +488,15 @@ pub(super) fn render_large_dropdown<'a>(
|
||||||
});
|
});
|
||||||
|
|
||||||
let arr_btn = button(
|
let arr_btn = button(
|
||||||
container(icons::arrow_down(9.0, ARROW_COLOR))
|
container(icons::themed_arrow_down(9.0))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
tool_btn_style(theme, dd_open, 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()
|
|
||||||
})
|
})
|
||||||
.width(Length::Fixed(LARGE_W))
|
.width(Length::Fixed(LARGE_W))
|
||||||
.height(LARGE_ARR)
|
.height(LARGE_ARR)
|
||||||
|
|
@ -659,13 +549,15 @@ pub(super) fn render_large<'a>(
|
||||||
let btn = button(
|
let btn = button(
|
||||||
column![
|
column![
|
||||||
make_icon_dim(t.icon, LARGE_ICON, dim),
|
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)
|
.align_x(iced::Center)
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
)
|
)
|
||||||
.on_press(Message::RibbonToolClick { tool_id, event })
|
.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))
|
.width(Length::Fixed(LARGE_W))
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
|
|
@ -737,10 +629,10 @@ pub(super) fn render_large<'a>(
|
||||||
let freeze_icon = icons::raw(icons::layer_freeze(lf), 14.0);
|
let freeze_icon = icons::raw(icons::layer_freeze(lf), 14.0);
|
||||||
let lock_icon = icons::raw(icons::layer_lock(ll), 14.0);
|
let lock_icon = icons::raw(icons::layer_lock(ll), 14.0);
|
||||||
let swatch = container(text(""))
|
let swatch = container(text(""))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(lc)),
|
background: Some(Background::Color(lc)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: SWATCH_BORDER,
|
color: theme.extended_palette().background.strong.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 1.0.into(),
|
radius: 1.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -758,27 +650,17 @@ pub(super) fn render_large<'a>(
|
||||||
freeze_icon,
|
freeze_icon,
|
||||||
lock_icon,
|
lock_icon,
|
||||||
swatch,
|
swatch,
|
||||||
container(text(active_layer).size(11).color(Color::WHITE))
|
container(text(active_layer).size(11))
|
||||||
.width(name_w)
|
.width(name_w)
|
||||||
.clip(true),
|
.clip(true),
|
||||||
icons::arrow_down(9.0, COMBO_ARROW),
|
icons::themed_arrow_down(9.0),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonDropdown(LAYER_COMBO_ID.to_string()))
|
.on_press(Message::ToggleRibbonDropdown(LAYER_COMBO_ID.to_string()))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (is_open, status) {
|
combo_btn_style(theme, is_open, status, 3.0)
|
||||||
(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()
|
|
||||||
})
|
})
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
@ -793,20 +675,16 @@ pub(super) fn render_large<'a>(
|
||||||
let event = t.event.clone();
|
let event = t.event.clone();
|
||||||
let icon_el: Element<Message> = if dim {
|
let icon_el: Element<Message> = if dim {
|
||||||
make_icon_dim(t.icon, 16.0, true)
|
make_icon_dim(t.icon, 16.0, true)
|
||||||
} else { match t.icon {
|
} else {
|
||||||
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
make_icon(t.icon, 16.0)
|
||||||
IconKind::Svg(bytes) => {
|
};
|
||||||
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
|
||||||
.width(16)
|
|
||||||
.height(16)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
} };
|
|
||||||
let msg = module_event_to_message(event);
|
let msg = module_event_to_message(event);
|
||||||
tooltip(
|
tooltip(
|
||||||
button(icon_el)
|
button(icon_el)
|
||||||
.on_press(msg)
|
.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]),
|
.padding([2, 5]),
|
||||||
make_tip(tip.to_string()),
|
make_tip(tip.to_string()),
|
||||||
TipPos::Right,
|
TipPos::Right,
|
||||||
|
|
@ -864,7 +742,9 @@ pub(super) fn render_large<'a>(
|
||||||
let mp_btn = button(
|
let mp_btn = button(
|
||||||
column![
|
column![
|
||||||
make_icon_dim(match_prop.icon, LARGE_ICON, mp_dim),
|
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)
|
.align_x(iced::Center)
|
||||||
.spacing(3),
|
.spacing(3),
|
||||||
|
|
@ -873,7 +753,7 @@ pub(super) fn render_large<'a>(
|
||||||
tool_id: mp_id,
|
tool_id: mp_id,
|
||||||
event: mp_event,
|
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))
|
.width(Length::Fixed(LARGE_W))
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
|
|
@ -895,10 +775,10 @@ pub(super) fn render_large<'a>(
|
||||||
let is_open = open_dd.as_deref() == Some(dd_id);
|
let is_open = open_dd.as_deref() == Some(dd_id);
|
||||||
let swatch_el: Element<'a, Message> = if let Some(c) = swatch {
|
let swatch_el: Element<'a, Message> = if let Some(c) = swatch {
|
||||||
container(text(""))
|
container(text(""))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(c)),
|
background: Some(Background::Color(c)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: SWATCH_BORDER,
|
color: theme.extended_palette().background.strong.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 1.0.into(),
|
radius: 1.0.into(),
|
||||||
},
|
},
|
||||||
|
|
@ -913,40 +793,21 @@ pub(super) fn render_large<'a>(
|
||||||
button(
|
button(
|
||||||
row![
|
row![
|
||||||
swatch_el,
|
swatch_el,
|
||||||
container(text(label).size(10).color(Color::WHITE))
|
container(text(label).size(10))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.clip(true),
|
.clip(true),
|
||||||
icons::arrow_toggle(
|
if is_open {
|
||||||
is_open,
|
icons::themed_arrow_up(8.0)
|
||||||
8.0,
|
} else {
|
||||||
Color {
|
icons::themed_arrow_down(8.0)
|
||||||
r: 0.6,
|
},
|
||||||
g: 0.6,
|
|
||||||
b: 0.6,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonDropdown(dd_id.to_string()))
|
.on_press(Message::ToggleRibbonDropdown(dd_id.to_string()))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (is_open, status) {
|
combo_btn_style(theme, is_open, status, 2.0)
|
||||||
(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()
|
|
||||||
})
|
})
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.width(Length::Fixed(PROP_W))
|
.width(Length::Fixed(PROP_W))
|
||||||
|
|
@ -999,31 +860,21 @@ pub(super) fn render_large<'a>(
|
||||||
// ── combo button ──
|
// ── combo button ──
|
||||||
let combo_btn = button(
|
let combo_btn = button(
|
||||||
row![
|
row![
|
||||||
container(text(active.clone()).size(11).color(Color::WHITE))
|
container(text(active.clone()).size(11))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.clip(true),
|
.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)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::ToggleRibbonDropdown(combo_id.to_string()))
|
.on_press(Message::ToggleRibbonDropdown(combo_id.to_string()))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (is_open, status) {
|
combo_btn_style(theme, is_open, status, 3.0)
|
||||||
(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()
|
|
||||||
})
|
})
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
@ -1045,20 +896,16 @@ pub(super) fn render_large<'a>(
|
||||||
let event = t.event.clone();
|
let event = t.event.clone();
|
||||||
let icon_el: Element<Message> = if dim {
|
let icon_el: Element<Message> = if dim {
|
||||||
make_icon_dim(t.icon, 16.0, true)
|
make_icon_dim(t.icon, 16.0, true)
|
||||||
} else { match t.icon {
|
} else {
|
||||||
IconKind::Glyph(g) => text(g).size(13).color(Color::WHITE).into(),
|
make_icon(t.icon, 16.0)
|
||||||
IconKind::Svg(bytes) => {
|
};
|
||||||
iced::widget::svg(iced::widget::svg::Handle::from_memory(bytes))
|
|
||||||
.width(16)
|
|
||||||
.height(16)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
} };
|
|
||||||
let msg = module_event_to_message(event);
|
let msg = module_event_to_message(event);
|
||||||
tooltip(
|
tooltip(
|
||||||
button(icon_el)
|
button(icon_el)
|
||||||
.on_press(msg)
|
.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]),
|
.padding([2, 5]),
|
||||||
make_tip(tip.to_string()),
|
make_tip(tip.to_string()),
|
||||||
TipPos::Right,
|
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).
|
// they read on the dark top strip (raw black is invisible there).
|
||||||
// On the Start tab, commands the start gate refuses render dimmed.
|
// On the Start tab, commands the start gate refuses render dimmed.
|
||||||
let dim = is_start && !crate::app::commands::start_allowed(cmd);
|
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(
|
let btn = button(
|
||||||
container(icon)
|
container(icon)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -1137,22 +988,7 @@ pub(super) fn quick_access_btn<'a>(
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(Message::Command(cmd.to_string()))
|
.on_press(Message::Command(cmd.to_string()))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(TOP_HIST_W))
|
.width(Length::Fixed(TOP_HIST_W))
|
||||||
.height(24)
|
.height(24)
|
||||||
.padding([2, 0]);
|
.padding([2, 0]);
|
||||||
|
|
@ -1171,13 +1007,12 @@ pub(super) fn render_history_control<'a>(
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let dd_open = open_dropdown.as_deref() == Some(dropdown_id);
|
let dd_open = open_dropdown.as_deref() == Some(dropdown_id);
|
||||||
let active = count > 0;
|
let active = count > 0;
|
||||||
let icon_color = if active { Color::WHITE } else { LABEL_OFF };
|
|
||||||
|
|
||||||
let main_btn = {
|
let main_btn = {
|
||||||
let glyph = if dropdown_id == UNDO_HISTORY_ID {
|
let glyph = if dropdown_id == UNDO_HISTORY_ID {
|
||||||
icons::undo(15.0, icon_color)
|
icons::themed_undo(15.0, active)
|
||||||
} else {
|
} else {
|
||||||
icons::redo(15.0, icon_color)
|
icons::themed_redo(15.0, active)
|
||||||
};
|
};
|
||||||
let btn = button(
|
let btn = button(
|
||||||
container(glyph)
|
container(glyph)
|
||||||
|
|
@ -1186,7 +1021,9 @@ pub(super) fn render_history_control<'a>(
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.align_y(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))
|
.width(Length::Fixed(TOP_HIST_W))
|
||||||
.height(24)
|
.height(24)
|
||||||
.padding([2, 0]);
|
.padding([2, 0]);
|
||||||
|
|
@ -1211,16 +1048,19 @@ pub(super) fn render_history_control<'a>(
|
||||||
|
|
||||||
let arrow_btn = {
|
let arrow_btn = {
|
||||||
let btn = button(
|
let btn = button(
|
||||||
container(icons::arrow_down(
|
container(if active {
|
||||||
8.0,
|
icons::themed_arrow_down(8.0)
|
||||||
if active { ARROW_COLOR } else { LABEL_OFF },
|
} else {
|
||||||
))
|
icons::themed_disabled_arrow_down(8.0)
|
||||||
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.align_y(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))
|
.width(Length::Fixed(TOP_ARR_W))
|
||||||
.height(24)
|
.height(24)
|
||||||
.padding(0);
|
.padding(0);
|
||||||
|
|
@ -1243,19 +1083,26 @@ pub(super) fn render_history_control<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn top_hist_btn_style(
|
pub(super) fn top_hist_btn_style(
|
||||||
|
theme: &Theme,
|
||||||
active: bool,
|
active: bool,
|
||||||
open: bool,
|
open: bool,
|
||||||
status: button::Status,
|
status: button::Status,
|
||||||
) -> button::Style {
|
) -> 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 {
|
button::Style {
|
||||||
background: Some(Background::Color(match (active, open, status) {
|
background: (!active || open || matches!(
|
||||||
(false, _, _) => HIST_INACTIVE_BG,
|
status,
|
||||||
(_, true, _) => TOOL_ACTIVE,
|
button::Status::Hovered | button::Status::Pressed
|
||||||
(_, _, button::Status::Hovered) => TOOL_HOVER,
|
))
|
||||||
(_, _, button::Status::Pressed) => TOOL_ACTIVE,
|
.then_some(Background::Color(pair.color)),
|
||||||
_ => Color::TRANSPARENT,
|
text_color: pair.text,
|
||||||
})),
|
|
||||||
text_color: Color::WHITE,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
color: Color::TRANSPARENT,
|
color: Color::TRANSPARENT,
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,12 @@
|
||||||
//! module's tools can drive it. First used for paper-space viewport / plot
|
//! module's tools can drive it. First used for paper-space viewport / plot
|
||||||
//! actions; reusable for any future context action set.
|
//! actions; reusable for any future context action set.
|
||||||
|
|
||||||
use iced::widget::{button, column, container, svg, text, tooltip};
|
use iced::widget::{button, column, container, text, tooltip};
|
||||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
use iced::{Background, Border, Element, Length, Theme};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::modules::{IconKind, ToolDef};
|
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 ICON_SIZE: f32 = 22.0;
|
||||||
const BTN_SIZE: f32 = 38.0;
|
const BTN_SIZE: f32 = 38.0;
|
||||||
/// Gap between the toolbar and the right edge of the canvas.
|
/// 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> {
|
fn icon_el(icon: IconKind) -> Element<'static, Message> {
|
||||||
match icon {
|
match icon {
|
||||||
IconKind::Glyph(s) => text(s).size(ICON_SIZE * 0.85).color(Color::WHITE).into(),
|
IconKind::Glyph(s) => text(s).size(ICON_SIZE * 0.85).into(),
|
||||||
IconKind::Svg(bytes) => svg(svg::Handle::from_memory(bytes))
|
IconKind::Svg(bytes) => crate::ui::icons::themed(bytes, ICON_SIZE),
|
||||||
.width(Length::Fixed(ICON_SIZE))
|
|
||||||
.height(Length::Fixed(ICON_SIZE))
|
|
||||||
.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tip_panel(label: &'static str) -> Element<'static, Message> {
|
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])
|
.padding([2, 6])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.strong.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: PANEL_BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
|
text_color: Some(palette.background.strong.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -63,17 +61,22 @@ pub fn view(tools: &[ToolDef]) -> Option<Element<'static, Message>> {
|
||||||
})
|
})
|
||||||
.width(Length::Fixed(BTN_SIZE))
|
.width(Length::Fixed(BTN_SIZE))
|
||||||
.height(Length::Fixed(BTN_SIZE))
|
.height(Length::Fixed(BTN_SIZE))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered | button::Status::Pressed => BTN_HOVER,
|
let hovered = matches!(
|
||||||
_ => Color::TRANSPARENT,
|
status,
|
||||||
})),
|
button::Status::Hovered | button::Status::Pressed
|
||||||
|
);
|
||||||
|
button::Style {
|
||||||
|
background: hovered
|
||||||
|
.then_some(Background::Color(palette.background.strong.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text_color: Color::WHITE,
|
text_color: palette.background.base.text,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Label tooltip on the left so it never runs off the right edge.
|
// Label tooltip on the left so it never runs off the right edge.
|
||||||
col = col.push(
|
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 {
|
let panel = container(col).padding(4).style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: PANEL_BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 5.0.into(),
|
radius: 5.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fill the canvas, pin the panel to the right edge and centre it
|
// 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
|
// Leftmost hamburger: opens a dropdown listing Model + every layout, so
|
||||||
// a layout can be picked directly even when the tab strip is scrolled.
|
// a layout can be picked directly even when the tab strip is scrolled.
|
||||||
let menu_button = button(crate::ui::icons::tinted(
|
let menu_icon = if is_start {
|
||||||
crate::ui::icons::MENU,
|
crate::ui::icons::themed_disabled(crate::ui::icons::MENU, 16.0)
|
||||||
16.0,
|
} else {
|
||||||
if is_start { DISABLED_COLOR } else { ICON_COLOR },
|
crate::ui::icons::themed_secondary(crate::ui::icons::MENU, 16.0)
|
||||||
))
|
};
|
||||||
.style(|_: &Theme, status| button::Style {
|
let menu_button = button(menu_icon)
|
||||||
background: Some(Background::Color(match status {
|
.style(button::subtle)
|
||||||
button::Status::Hovered => PILL_BG,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
border: Border {
|
|
||||||
radius: 3.0.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 8]);
|
.padding([4, 8]);
|
||||||
let menu_btn = if is_start {
|
let menu_btn = if is_start {
|
||||||
tip(
|
tip(
|
||||||
|
|
@ -156,15 +147,8 @@ impl StatusBar {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let add_button = button(text("+").size(12).color(if is_start {
|
let add_button = button(text("+").size(12))
|
||||||
DISABLED_COLOR
|
.style(button::subtle)
|
||||||
} else {
|
|
||||||
ICON_COLOR
|
|
||||||
}))
|
|
||||||
.style(|_: &Theme, _| button::Style {
|
|
||||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 8]);
|
.padding([4, 8]);
|
||||||
let add_btn = if is_start {
|
let add_btn = if is_start {
|
||||||
tip(
|
tip(
|
||||||
|
|
@ -453,14 +437,17 @@ impl StatusBar {
|
||||||
.justify_end(true);
|
.justify_end(true);
|
||||||
|
|
||||||
container(wrap)
|
container(wrap)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(BAR_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
// One row matches the drawing (document) tab bar height so the three
|
// 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 ──────────────────────────────────────────────────
|
// ── Customization handle ──────────────────────────────────────────────────
|
||||||
|
|
||||||
fn customize_btn() -> Element<'static, Message> {
|
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))
|
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => PILL_BG,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
border: Border {
|
|
||||||
radius: 3.0.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -521,7 +498,7 @@ fn customize_btn() -> Element<'static, Message> {
|
||||||
// ── Tooltip helper ────────────────────────────────────────────────────────
|
// ── Tooltip helper ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn tip<'a>(content: Element<'a, Message>, label: &'static str) -> Element<'a, Message> {
|
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
|
/// 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(
|
tooltip(
|
||||||
content,
|
content,
|
||||||
container(body)
|
container(body)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([4, 8]),
|
.padding([4, 8]),
|
||||||
TipPos::Top,
|
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
|
/// text labels were too small to read). The name lives in the tooltip each
|
||||||
/// call site already wraps it with.
|
/// call site already wraps it with.
|
||||||
fn toggle_pill(icon: &'static [u8], active: bool, msg: Message) -> Element<'static, Message> {
|
fn toggle_pill(icon: &'static [u8], active: bool, msg: Message) -> Element<'static, Message> {
|
||||||
let color = if active { OSNAP_ON_TEXT } else { OSNAP_OFF_TEXT };
|
let icon = if active {
|
||||||
button(crate::ui::icons::tinted(icon, 17.0, color))
|
crate::ui::icons::themed_primary(icon, 17.0)
|
||||||
|
} else {
|
||||||
|
crate::ui::icons::themed_secondary(icon, 17.0)
|
||||||
|
};
|
||||||
|
button(icon)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (active, status) {
|
let mut style = button::subtle(theme, status);
|
||||||
(true, button::Status::Hovered) => SNAP_ON_HOVER,
|
if active {
|
||||||
(true, _) => SNAP_ON_BG,
|
let palette = theme.extended_palette();
|
||||||
(false, button::Status::Hovered) => SNAP_OFF_HOVER,
|
style.background = Some(Background::Color(match status {
|
||||||
(false, _) => SNAP_OFF_BG,
|
button::Status::Hovered => palette.primary.base.color,
|
||||||
})),
|
_ => palette.primary.weak.color,
|
||||||
border: Border {
|
}));
|
||||||
color: if active { SNAP_BORDER_ON } else { BORDER_COLOR },
|
style.text_color = palette.primary.weak.text;
|
||||||
width: 1.0,
|
style.border.color = palette.primary.base.color;
|
||||||
radius: 2.0.into(),
|
style.border.width = 1.0;
|
||||||
},
|
}
|
||||||
text_color: color,
|
style
|
||||||
shadow: iced::Shadow::default(),
|
|
||||||
snap: false,
|
|
||||||
})
|
})
|
||||||
.padding([4, 7])
|
.padding([4, 7])
|
||||||
.into()
|
.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
|
// and a dropdown caret, so both halves read as a single integrated control and
|
||||||
// the border / background / caret / sizing live in exactly ONE place.
|
// 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
|
/// Wrap a caller-built, already-click-wired `main` element together with its
|
||||||
/// menu-bearing dropdown caret.
|
/// menu-bearing dropdown caret.
|
||||||
fn split_pill<'a>(
|
fn split_pill<'a>(
|
||||||
|
|
@ -628,22 +578,26 @@ fn split_pill<'a>(
|
||||||
caret: Element<'a, Message>,
|
caret: Element<'a, Message>,
|
||||||
active: bool,
|
active: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> 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))
|
container(row![main, caret].spacing(3).align_y(iced::Center))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(bg)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(if active {
|
||||||
|
palette.primary.weak.color
|
||||||
|
} else {
|
||||||
|
palette.background.weakest.color
|
||||||
|
})),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: border_color,
|
color: if active {
|
||||||
|
palette.primary.base.color
|
||||||
|
} else {
|
||||||
|
palette.background.neutral.color
|
||||||
|
},
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -664,7 +618,6 @@ fn polar_pill<'a>(
|
||||||
let tooltip_text = format!(
|
let tooltip_text = format!(
|
||||||
"Polar Tracking ({angle})\nF10 — left-click on/off\nRight-click cycles · ▾ picks angle",
|
"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.
|
// 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];
|
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,
|
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(
|
let main = mouse_area(
|
||||||
row![
|
row![
|
||||||
crate::ui::icons::tinted(crate::ui::icons::ST_POLAR, 17.0, color),
|
polar_icon,
|
||||||
text(angle).size(11).color(color),
|
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)
|
.spacing(2)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -685,26 +652,8 @@ fn polar_pill<'a>(
|
||||||
.on_right_press(Message::SetPolarAngle(next_angle));
|
.on_right_press(Message::SetPolarAngle(next_angle));
|
||||||
let main = tooltip(
|
let main = tooltip(
|
||||||
main,
|
main,
|
||||||
container(text(tooltip_text).size(11).color(Color::WHITE))
|
container(text(tooltip_text).size(11))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([4, 8]),
|
.padding([4, 8]),
|
||||||
TipPos::Top,
|
TipPos::Top,
|
||||||
);
|
);
|
||||||
|
|
@ -712,7 +661,12 @@ fn polar_pill<'a>(
|
||||||
let caret = status_menu::menu_bar(
|
let caret = status_menu::menu_bar(
|
||||||
menu_tip(
|
menu_tip(
|
||||||
mouse_area(
|
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))
|
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -737,12 +691,13 @@ fn osnap_btn<'a>(
|
||||||
entries: Vec<StatusMenuEntry<'a>>,
|
entries: Vec<StatusMenuEntry<'a>>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let on = active || snap_enabled;
|
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(
|
let main = tip(
|
||||||
mouse_area(crate::ui::icons::tinted(
|
mouse_area(snap_icon)
|
||||||
crate::ui::icons::ST_OSNAP,
|
|
||||||
17.0,
|
|
||||||
pill_text_color(on),
|
|
||||||
))
|
|
||||||
.on_press(Message::ToggleSnapEnabled)
|
.on_press(Message::ToggleSnapEnabled)
|
||||||
.into(),
|
.into(),
|
||||||
"Object Snap: toggle on/off\nF3",
|
"Object Snap: toggle on/off\nF3",
|
||||||
|
|
@ -751,7 +706,12 @@ fn osnap_btn<'a>(
|
||||||
let caret = status_menu::menu_bar(
|
let caret = status_menu::menu_bar(
|
||||||
menu_tip(
|
menu_tip(
|
||||||
mouse_area(
|
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))
|
.on_press(Message::StatusMenuTooltipHidden(true))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -768,44 +728,10 @@ fn osnap_btn<'a>(
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn layout_tab_context_menu(name: String) -> Element<'static, Message> {
|
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| {
|
let item = |label: &'static str, msg: Message| {
|
||||||
button(text(label).size(12).color(TEXT_COLOR))
|
button(text(label).size(12))
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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,
|
|
||||||
})
|
|
||||||
.padding([4, 12])
|
.padding([4, 12])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
};
|
};
|
||||||
|
|
@ -818,15 +744,7 @@ fn layout_tab_context_menu(name: String) -> Element<'static, Message> {
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
.width(160),
|
.width(160),
|
||||||
)
|
)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(MENU_BG)),
|
|
||||||
border: Border {
|
|
||||||
color: MENU_BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 0])
|
.padding([4, 0])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -843,46 +761,34 @@ fn space_tab<'a>(
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
reorderable_layouts: Arc<[String]>,
|
reorderable_layouts: Arc<[String]>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let bg = move |is_active: bool, hovered: bool| {
|
let tab_style = move |theme: &Theme| {
|
||||||
if is_active {
|
let palette = theme.extended_palette();
|
||||||
TAB_ACTIVE
|
let text_color = if !enabled {
|
||||||
} else if hovered {
|
palette.background.base.text.scale_alpha(0.42)
|
||||||
TAB_HOVER
|
} else if is_active {
|
||||||
|
palette.primary.weak.text
|
||||||
} else {
|
} else {
|
||||||
Color::TRANSPARENT
|
palette.background.base.text.scale_alpha(0.72)
|
||||||
}
|
};
|
||||||
};
|
container::Style {
|
||||||
|
background: is_active.then_some(Background::Color(palette.primary.weak.color)),
|
||||||
let border = Border {
|
border: Border {
|
||||||
color: if is_active {
|
color: if is_active {
|
||||||
ACCENT
|
palette.primary.base.color
|
||||||
} else {
|
} else {
|
||||||
Color::TRANSPARENT
|
Color::TRANSPARENT
|
||||||
},
|
},
|
||||||
width: if is_active { 1.0 } else { 0.0 },
|
width: if is_active { 1.0 } else { 0.0 },
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
};
|
},
|
||||||
|
text_color: Some(text_color),
|
||||||
let text_color = if !enabled {
|
..Default::default()
|
||||||
DISABLED_COLOR
|
|
||||||
} else if is_active {
|
|
||||||
Color::WHITE
|
|
||||||
} else {
|
|
||||||
Color {
|
|
||||||
r: 0.65,
|
|
||||||
g: 0.65,
|
|
||||||
b: 0.65,
|
|
||||||
a: 1.0,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !enabled {
|
if !enabled {
|
||||||
let display = container(text(label.clone()).size(12).color(text_color))
|
let display = container(text(label.clone()).size(12))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(tab_style)
|
||||||
background: Some(Background::Color(bg(is_active, false))),
|
|
||||||
border,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 10]);
|
.padding([4, 10]);
|
||||||
crate::ui::wrap_bar::PosReport::owned(
|
crate::ui::wrap_bar::PosReport::owned(
|
||||||
format!("SB_LAYOUT_TAB:{label}"),
|
format!("SB_LAYOUT_TAB:{label}"),
|
||||||
|
|
@ -899,49 +805,15 @@ fn space_tab<'a>(
|
||||||
.on_input(Message::LayoutRenameEdit)
|
.on_input(Message::LayoutRenameEdit)
|
||||||
.on_submit(Message::LayoutRenameCommit)
|
.on_submit(Message::LayoutRenameCommit)
|
||||||
.size(12)
|
.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])
|
.padding([3, 6])
|
||||||
.width(Length::Fixed(90.0));
|
.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,
|
crate::ui::icons::CLOSE,
|
||||||
10.0,
|
10.0,
|
||||||
Color {
|
|
||||||
r: 0.65,
|
|
||||||
g: 0.65,
|
|
||||||
b: 0.65,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
.on_press(Message::LayoutRenameCancel)
|
.on_press(Message::LayoutRenameCancel)
|
||||||
.style(|_: &Theme, _| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
|
||||||
border: Border::default(),
|
|
||||||
shadow: iced::Shadow::default(),
|
|
||||||
snap: false,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 4]);
|
.padding([4, 4]);
|
||||||
|
|
||||||
row![input, cancel_btn]
|
row![input, cancel_btn]
|
||||||
|
|
@ -951,12 +823,8 @@ fn space_tab<'a>(
|
||||||
} else {
|
} else {
|
||||||
// Normal clickable tab — left click switches. Paper-layout tabs are
|
// Normal clickable tab — left click switches. Paper-layout tabs are
|
||||||
// wrapped in `ContextMenu`, which owns right-click handling.
|
// wrapped in `ContextMenu`, which owns right-click handling.
|
||||||
let display = container(text(label.clone()).size(12).color(text_color))
|
let display = container(text(label.clone()).size(12))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(tab_style)
|
||||||
background: Some(Background::Color(bg(is_active, false))),
|
|
||||||
border,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 10]);
|
.padding([4, 10]);
|
||||||
|
|
||||||
let switch_msg = Message::LayoutSwitch(label.clone());
|
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))
|
("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 clickable = on_press.is_some();
|
||||||
let mut btn = button(text(label).size(12).color(text_color))
|
let mut btn = button(text(label).size(12))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
let mut style = button::subtle(theme, status);
|
||||||
button::Status::Hovered if clickable => bg_hover,
|
if active {
|
||||||
_ => bg_normal,
|
let palette = theme.extended_palette();
|
||||||
})),
|
style.background = Some(Background::Color(match status {
|
||||||
border: Border {
|
button::Status::Hovered if clickable => palette.primary.base.color,
|
||||||
color: border_color,
|
_ => palette.primary.weak.color,
|
||||||
width: 1.0,
|
}));
|
||||||
radius: 2.0.into(),
|
style.text_color = palette.primary.weak.text;
|
||||||
},
|
style.border.color = palette.primary.base.color;
|
||||||
text_color,
|
style.border.width = 1.0;
|
||||||
shadow: iced::Shadow::default(),
|
}
|
||||||
snap: false,
|
style
|
||||||
})
|
})
|
||||||
.padding([4, 7]);
|
.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> {
|
fn status_pill(label: impl Into<String>) -> Element<'static, Message> {
|
||||||
container(text(label.into()).size(12).color(Color {
|
container(text(label.into()).size(12))
|
||||||
r: 0.65,
|
.style(container::bordered_box)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.into()
|
.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 ────────────────────────────────────────────────────
|
// ── Scale popup button ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Shared visual root for labelled status-bar menus (units, scale, …).
|
/// 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> {
|
fn action_pill(label: &str, msg: Message) -> Element<'static, Message> {
|
||||||
let label = label.to_string();
|
let label = label.to_string();
|
||||||
button(
|
button(text(label).size(12))
|
||||||
text(label)
|
|
||||||
.size(12)
|
|
||||||
.color(OSNAP_OFF_TEXT),
|
|
||||||
)
|
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
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,
|
|
||||||
})
|
|
||||||
.padding([4, 7])
|
.padding([4, 7])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,57 +51,30 @@ pub fn menu_bar<'a>(
|
||||||
.safe_bounds_margin(0.0)
|
.safe_bounds_margin(0.0)
|
||||||
.close_on_background_click_global(true)
|
.close_on_background_click_global(true)
|
||||||
.draw_path(DrawPath::Backdrop)
|
.draw_path(DrawPath::Backdrop)
|
||||||
.style(|_: &Theme, _| iced_aw::style::menu_bar::Style {
|
.style(|theme: &Theme, _| {
|
||||||
bar_background: Background::Color(Color::TRANSPARENT),
|
let palette = theme.extended_palette();
|
||||||
bar_border: Border::default(),
|
iced_aw::style::menu_bar::Style {
|
||||||
bar_shadow: Shadow::default(),
|
bar_background: Background::Color(Color::TRANSPARENT),
|
||||||
menu_background: Background::Color(MENU_BG),
|
bar_border: Border::default(),
|
||||||
menu_border: Border {
|
bar_shadow: Shadow::default(),
|
||||||
color: MENU_BORDER,
|
menu_background: Background::Color(palette.background.weakest.color),
|
||||||
width: 1.0,
|
menu_border: Border {
|
||||||
radius: 3.0.into(),
|
color: palette.background.neutral.color,
|
||||||
},
|
width: 1.0,
|
||||||
menu_shadow: Shadow {
|
radius: 3.0.into(),
|
||||||
color: Color {
|
|
||||||
r: 0.0,
|
|
||||||
g: 0.0,
|
|
||||||
b: 0.0,
|
|
||||||
a: 0.35,
|
|
||||||
},
|
},
|
||||||
offset: iced::Vector::new(0.0, -2.0),
|
menu_shadow: Shadow {
|
||||||
blur_radius: 6.0,
|
color: palette.background.strongest.color.scale_alpha(0.35),
|
||||||
},
|
offset: iced::Vector::new(0.0, -2.0),
|
||||||
path: Background::Color(ACTIVE_BG),
|
blur_radius: 6.0,
|
||||||
path_border: Border {
|
},
|
||||||
color: ACTIVE_BORDER,
|
path: Background::Color(palette.primary.weak.color),
|
||||||
width: 1.0,
|
path_border: Border {
|
||||||
radius: 2.0.into(),
|
color: palette.primary.base.color,
|
||||||
},
|
width: 1.0,
|
||||||
|
radius: 2.0.into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.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.
|
//! Status-bar customization and layout-list menu entries.
|
||||||
|
|
||||||
use iced::widget::{button, row, text};
|
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::app::Message;
|
||||||
use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
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> {
|
fn layout_row<'a>(name: String, is_current: bool) -> Element<'a, Message> {
|
||||||
let lbl = text(name.clone())
|
let lbl = text(name.clone()).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if is_current { LABEL_ON } else { LABEL_OFF });
|
|
||||||
button(row![lbl].align_y(iced::Center))
|
button(row![lbl].align_y(iced::Center))
|
||||||
.on_press(Message::LayoutSwitch(name))
|
.on_press(Message::LayoutSwitch(name))
|
||||||
.style(move |_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (is_current, status) {
|
let mut style = button::subtle(theme, status);
|
||||||
(_, button::Status::Hovered) => ROW_HOVER,
|
if is_current && status == button::Status::Active {
|
||||||
(true, _) => Color {
|
let palette = theme.extended_palette();
|
||||||
r: 0.18,
|
style.background = Some(Background::Color(palette.primary.weak.color));
|
||||||
g: 0.26,
|
style.text_color = palette.primary.weak.text;
|
||||||
b: 0.36,
|
}
|
||||||
a: 1.0,
|
style
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 12])
|
.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> {
|
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)
|
let lbl = text(label).size(11);
|
||||||
.size(11)
|
|
||||||
.color(if checked { LABEL_ON } else { LABEL_OFF });
|
|
||||||
|
|
||||||
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
let content = row![check, lbl].spacing(6).align_y(iced::Center);
|
||||||
|
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(button::subtle)
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => ROW_HOVER,
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.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.
|
//! the style / scale managers' frame so it looks consistent.
|
||||||
|
|
||||||
use crate::app::Message;
|
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::widget::{column, container, mouse_area, row, scrollable, text, Space};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, Element, Fill, Theme};
|
||||||
|
|
||||||
const MEMBER_CHECK: Color = Color {
|
|
||||||
r: 0.30,
|
|
||||||
g: 0.82,
|
|
||||||
b: 0.36,
|
|
||||||
a: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// `scales` is `(name, "paper:drawing" ratio, is_member)`. Every label is cloned
|
/// `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.
|
/// into the widget tree, so the returned element borrows nothing from the args.
|
||||||
|
|
@ -26,17 +19,17 @@ pub fn view_window(
|
||||||
) -> Element<'static, Message> {
|
) -> Element<'static, Message> {
|
||||||
let toolbar = container(
|
let toolbar = container(
|
||||||
row![
|
row![
|
||||||
text(format!("Object: {object_label}"))
|
text(format!("Object: {object_label}")).size(11),
|
||||||
.size(11)
|
|
||||||
.color(TEXT),
|
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
tb_button("Close", Message::CloseModal, true),
|
tb_button("Close", Message::CloseModal, true),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -45,21 +38,17 @@ pub fn view_window(
|
||||||
let rows: Vec<Element<'_, Message>> = scales
|
let rows: Vec<Element<'_, Message>> = scales
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, ratio, member)| {
|
.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![
|
let label = row![
|
||||||
check,
|
check,
|
||||||
text(name.clone()).size(11).color(TEXT).width(Fill),
|
text(name.clone()).size(11).width(Fill),
|
||||||
text(ratio.clone()).size(10).color(DIM),
|
text(ratio.clone()).size(10).style(muted_text_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
let cell = container(label)
|
let cell = container(label)
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.width(Fill)
|
.width(Fill);
|
||||||
.style(move |_: &Theme| container::Style {
|
|
||||||
text_color: Some(TEXT),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
mouse_area(cell)
|
mouse_area(cell)
|
||||||
.on_press(Message::AnnoObjectScaleToggle(name.clone()))
|
.on_press(Message::AnnoObjectScaleToggle(name.clone()))
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -67,14 +56,17 @@ pub fn view_window(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let list = container(scrollable(column(rows).spacing(1)).height(Fill))
|
let list = container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
@ -84,7 +76,7 @@ pub fn view_window(
|
||||||
column![
|
column![
|
||||||
text("Click a scale to add or remove the object's representation for it.")
|
text("Click a scale to add or remove the object's representation for it.")
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(DIM),
|
.style(muted_text_style),
|
||||||
list,
|
list,
|
||||||
]
|
]
|
||||||
.spacing(6)
|
.spacing(6)
|
||||||
|
|
@ -95,8 +87,10 @@ pub fn view_window(
|
||||||
.padding(12);
|
.padding(12);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body])
|
container(column![toolbar, hdivider(), body])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -4,44 +4,7 @@ use crate::app::{ColorPickTarget, DsField, Message};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
button, checkbox, 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};
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// All DimStyle field values needed by the view.
|
/// All DimStyle field values needed by the view.
|
||||||
pub struct DimStyleValues<'a> {
|
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 {
|
fn tab_btn_style(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (active, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, _) => ACTIVE,
|
let pair = match (active, st) {
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
(true, _) => palette.primary.strong,
|
||||||
r: 0.28,
|
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||||
g: 0.28,
|
palette.background.strong
|
||||||
b: 0.28,
|
}
|
||||||
a: 1.0,
|
_ => palette.background.weak,
|
||||||
},
|
};
|
||||||
_ => Color {
|
button::Style {
|
||||||
r: 0.20,
|
background: Some(Background::Color(pair.color)),
|
||||||
g: 0.20,
|
text_color: pair.text,
|
||||||
b: 0.20,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..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 {
|
text_input::Style {
|
||||||
background: Background::Color(FIELD),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: border,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
icon: TEXT,
|
icon: palette.background.base.text,
|
||||||
placeholder: DIM,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: TEXT,
|
value: palette.background.base.text,
|
||||||
selection: ACCENT,
|
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))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -221,7 +199,7 @@ pub fn view_window<'a>(
|
||||||
]
|
]
|
||||||
.spacing(2);
|
.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> {
|
let mk_field = |fld: DsField, val: &'a str| -> Element<'a, Message> {
|
||||||
text_input("", val)
|
text_input("", val)
|
||||||
|
|
@ -338,7 +316,7 @@ pub fn view_window<'a>(
|
||||||
|
|
||||||
let tab_content: Element<'_, Message> = match tab {
|
let tab_content: Element<'_, Message> = match tab {
|
||||||
0 => column![
|
0 => column![
|
||||||
text("Dimension Line").size(11).color(ACCENT),
|
text("Dimension Line").size(11).style(primary_style),
|
||||||
row![
|
row![
|
||||||
lbl("Extension (DIMDLE)"),
|
lbl("Extension (DIMDLE)"),
|
||||||
mk_field(DsField::Dimdle, vals.dimdle)
|
mk_field(DsField::Dimdle, vals.dimdle)
|
||||||
|
|
@ -359,7 +337,7 @@ pub fn view_window<'a>(
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
chk("Suppress 1st line (DIMSD1)", vals.dimsd1, DsField::Dimsd1),
|
chk("Suppress 1st line (DIMSD1)", vals.dimsd1, DsField::Dimsd1),
|
||||||
chk("Suppress 2nd line (DIMSD2)", vals.dimsd2, DsField::Dimsd2),
|
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![
|
row![
|
||||||
lbl("Extension (DIMEXE)"),
|
lbl("Extension (DIMEXE)"),
|
||||||
mk_field(DsField::Dimexe, vals.dimexe)
|
mk_field(DsField::Dimexe, vals.dimexe)
|
||||||
|
|
@ -421,7 +399,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
1 => column![
|
1 => column![
|
||||||
text("Arrows").size(11).color(ACCENT),
|
text("Arrows").size(11).style(primary_style),
|
||||||
hrow(
|
hrow(
|
||||||
"Arrowhead (DIMBLK)",
|
"Arrowhead (DIMBLK)",
|
||||||
vals.block_opts.clone(),
|
vals.block_opts.clone(),
|
||||||
|
|
@ -485,7 +463,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
2 => column![
|
2 => column![
|
||||||
text("Text").size(11).color(ACCENT),
|
text("Text").size(11).style(primary_style),
|
||||||
row![
|
row![
|
||||||
lbl("Height (DIMTXT)"),
|
lbl("Height (DIMTXT)"),
|
||||||
mk_field(DsField::Dimtxt, vals.dimtxt)
|
mk_field(DsField::Dimtxt, vals.dimtxt)
|
||||||
|
|
@ -551,7 +529,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
3 => column![
|
3 => column![
|
||||||
text("Scale").size(11).color(ACCENT),
|
text("Scale").size(11).style(primary_style),
|
||||||
chk("Annotative", vals.annotative, DsField::Annotative),
|
chk("Annotative", vals.annotative, DsField::Annotative),
|
||||||
row![
|
row![
|
||||||
lbl("Overall scale (DIMSCALE)"),
|
lbl("Overall scale (DIMSCALE)"),
|
||||||
|
|
@ -565,7 +543,7 @@ pub fn view_window<'a>(
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.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),
|
enum_field("Format (DIMLUNIT)", DsField::Dimlunit, vals.dimlunit, OPT_LUNIT),
|
||||||
row![
|
row![
|
||||||
lbl("Decimals (DIMDEC)"),
|
lbl("Decimals (DIMDEC)"),
|
||||||
|
|
@ -636,7 +614,7 @@ pub fn view_window<'a>(
|
||||||
("3", "Leading & trailing"),
|
("3", "Leading & trailing"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
text("Fit").size(11).color(ACCENT),
|
text("Fit").size(11).style(primary_style),
|
||||||
enum_field(
|
enum_field(
|
||||||
"Fit (DIMATFIT)",
|
"Fit (DIMATFIT)",
|
||||||
DsField::Dimatfit,
|
DsField::Dimatfit,
|
||||||
|
|
@ -680,7 +658,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
5 => column![
|
5 => column![
|
||||||
text("Alternate Units").size(11).color(ACCENT),
|
text("Alternate Units").size(11).style(primary_style),
|
||||||
chk(
|
chk(
|
||||||
"Enable alternate units (DIMALT)",
|
"Enable alternate units (DIMALT)",
|
||||||
vals.dimalt,
|
vals.dimalt,
|
||||||
|
|
@ -728,7 +706,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
_ => column![
|
_ => column![
|
||||||
text("Tolerances").size(11).color(ACCENT),
|
text("Tolerances").size(11).style(primary_style),
|
||||||
chk("Generate tolerances (DIMTOL)", vals.dimtol, DsField::Dimtol),
|
chk("Generate tolerances (DIMTOL)", vals.dimtol, DsField::Dimtol),
|
||||||
chk("Limits generation (DIMLIM)", vals.dimlim, DsField::Dimlim),
|
chk("Limits generation (DIMLIM)", vals.dimlim, DsField::Dimlim),
|
||||||
row![
|
row![
|
||||||
|
|
@ -773,7 +751,7 @@ pub fn view_window<'a>(
|
||||||
// content container instead of the panel padding.
|
// content container instead of the panel padding.
|
||||||
let right_panel = container(
|
let right_panel = container(
|
||||||
column![
|
column![
|
||||||
text(format!("Editing: {selected}")).size(11).color(DIM),
|
text(format!("Editing: {selected}")).size(11).style(muted_style),
|
||||||
tabs,
|
tabs,
|
||||||
hdivider(),
|
hdivider(),
|
||||||
scrollable(container(tab_content).padding([12, 12]).width(Fill))
|
scrollable(container(tab_content).padding([12, 12]).width(Fill))
|
||||||
|
|
|
||||||
|
|
@ -4,63 +4,41 @@ use crate::app::Message;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input,
|
button, checkbox, column, container, pick_list, row, scrollable, text, text_input,
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (accent, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
let pair = match (accent, st) {
|
||||||
r: 0.20,
|
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||||
g: 0.42,
|
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||||
b: 0.72,
|
palette.background.strong
|
||||||
a: 1.0,
|
}
|
||||||
},
|
(true, _) => palette.primary.base,
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
_ => palette.background.weak,
|
||||||
r: 0.28,
|
};
|
||||||
g: 0.28,
|
button::Style {
|
||||||
b: 0.28,
|
background: Some(Background::Color(pair.color)),
|
||||||
a: 1.0,
|
text_color: pair.text,
|
||||||
},
|
|
||||||
(true, _) => ACCENT,
|
|
||||||
_ => Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.22,
|
|
||||||
b: 0.22,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..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> {
|
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>(
|
fn num_row<'a>(
|
||||||
|
|
@ -116,7 +94,7 @@ fn num_row<'a>(
|
||||||
field: &'static str,
|
field: &'static str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(150),
|
text(label).size(11).style(muted_style).width(150),
|
||||||
text_input(placeholder, value)
|
text_input(placeholder, value)
|
||||||
.on_input(move |v| Message::MLeaderStyleEdit { field, value: v })
|
.on_input(move |v| Message::MLeaderStyleEdit { field, value: v })
|
||||||
.size(11)
|
.size(11)
|
||||||
|
|
@ -150,7 +128,7 @@ fn color_row<'a>(
|
||||||
Message::MLeaderColorMore(field),
|
Message::MLeaderColorMore(field),
|
||||||
Message::OpenColorWindow(crate::app::ColorPickTarget::MLeader(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)
|
.spacing(8)
|
||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -163,7 +141,7 @@ fn enum_row<'a>(
|
||||||
field: &'static str,
|
field: &'static str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
row![
|
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| {
|
pick_list(options, Some(selected), move |value| {
|
||||||
Message::MLeaderStyleSetEnum { field, value }
|
Message::MLeaderStyleSetEnum { field, value }
|
||||||
})
|
})
|
||||||
|
|
@ -178,7 +156,7 @@ fn enum_row<'a>(
|
||||||
fn lineweight_row<'a>(selected: acadrust::types::LineWeight) -> Element<'a, Message> {
|
fn lineweight_row<'a>(selected: acadrust::types::LineWeight) -> Element<'a, Message> {
|
||||||
let selected = crate::ui::properties::LwItem(selected);
|
let selected = crate::ui::properties::LwItem(selected);
|
||||||
row![
|
row![
|
||||||
text("Line weight:").size(11).color(DIM).width(150),
|
text("Line weight:").size(11).style(muted_style).width(150),
|
||||||
pick_list(
|
pick_list(
|
||||||
crate::ui::properties::lw_options(),
|
crate::ui::properties::lw_options(),
|
||||||
Some(selected),
|
Some(selected),
|
||||||
|
|
@ -220,7 +198,7 @@ fn handle_row<'a>(
|
||||||
field: &'static str,
|
field: &'static str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
row![
|
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| {
|
pick_list(options, Some(selected), move |value| {
|
||||||
Message::MLeaderStyleSetHandle { field, value }
|
Message::MLeaderStyleSetHandle { field, value }
|
||||||
})
|
})
|
||||||
|
|
@ -247,7 +225,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
scrollable(
|
scrollable(
|
||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
text("Name:").size(11).color(DIM).width(150),
|
text("Name:").size(11).style(muted_style).width(150),
|
||||||
text(s.name.clone()).size(11),
|
text(s.name.clone()).size(11),
|
||||||
]
|
]
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
|
|
@ -422,7 +400,7 @@ pub fn view_window<'a>(v: MLeaderStyleView<'a>) -> Element<'a, Message> {
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} 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])
|
.padding([12, 12])
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{column, container, row, scrollable, text};
|
use iced::widget::{column, container, row, scrollable, text};
|
||||||
use iced::{Color, Element, Fill};
|
use iced::{Element, Fill, Theme};
|
||||||
|
|
||||||
const DIM: Color = Color {
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.55,
|
iced::widget::text::Style {
|
||||||
g: 0.55,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
b: 0.55,
|
}
|
||||||
a: 1.0,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
pub fn view_window<'a>(
|
pub fn view_window<'a>(
|
||||||
styles: Vec<String>,
|
styles: Vec<String>,
|
||||||
|
|
@ -22,7 +21,7 @@ pub fn view_window<'a>(
|
||||||
// ── Right: Details panel ──────────────────────────────────────────────
|
// ── Right: Details panel ──────────────────────────────────────────────
|
||||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(120),
|
text(label).size(11).style(muted_style).width(120),
|
||||||
text(val).size(11),
|
text(val).size(11),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
@ -49,7 +48,7 @@ pub fn view_window<'a>(
|
||||||
&e.linetype
|
&e.linetype
|
||||||
};
|
};
|
||||||
row![
|
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(format!("{:+.3}", e.offset)).size(10).width(70),
|
||||||
text(color_str).size(10).width(90),
|
text(color_str).size(10).width(90),
|
||||||
text(lt).size(10),
|
text(lt).size(10),
|
||||||
|
|
@ -65,7 +64,7 @@ pub fn view_window<'a>(
|
||||||
info_row("Elements:", s.elements.len().to_string()),
|
info_row("Elements:", s.elements.len().to_string()),
|
||||||
text(" Off Color Ltype")
|
text(" Off Color Ltype")
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(DIM)
|
.style(muted_style)
|
||||||
.into(),
|
.into(),
|
||||||
];
|
];
|
||||||
col_items.extend(elem_rows);
|
col_items.extend(elem_rows);
|
||||||
|
|
@ -73,7 +72,7 @@ pub fn view_window<'a>(
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} 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])
|
.padding([12, 12])
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,108 +2,55 @@
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (accent, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
let pair = match (accent, st) {
|
||||||
r: 0.20,
|
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||||
g: 0.42,
|
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||||
b: 0.72,
|
palette.background.strong
|
||||||
a: 1.0,
|
}
|
||||||
},
|
(true, _) => palette.primary.base,
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
_ => palette.background.weak,
|
||||||
r: 0.28,
|
};
|
||||||
g: 0.28,
|
button::Style {
|
||||||
b: 0.28,
|
background: Some(Background::Color(pair.color)),
|
||||||
a: 1.0,
|
text_color: pair.text,
|
||||||
},
|
|
||||||
(true, _) => ACCENT,
|
|
||||||
_ => Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.22,
|
|
||||||
b: 0.22,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..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 {
|
text_input::Style {
|
||||||
background: Background::Color(FIELD),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: border,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
icon: TEXT,
|
icon: palette.background.base.text,
|
||||||
placeholder: DIM,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: TEXT,
|
value: palette.background.base.text,
|
||||||
selection: ACCENT,
|
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))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -122,8 +71,10 @@ fn vsep<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(1).height(Fill))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -156,13 +107,15 @@ pub fn view_window<'a>(
|
||||||
.style(btn_s(false))
|
.style(btn_s(false))
|
||||||
.padding([4, 10]),
|
.padding([4, 10]),
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
text(table_name).size(10).color(DIM),
|
text(table_name).size(10).style(muted_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -199,19 +152,22 @@ pub fn view_window<'a>(
|
||||||
};
|
};
|
||||||
button(text(label).size(10).font(iced::Font::MONOSPACE))
|
button(text(label).size(10).font(iced::Font::MONOSPACE))
|
||||||
.on_press(Message::PlotStylePanelSelectAci(aci))
|
.on_press(Message::PlotStylePanelSelectAci(aci))
|
||||||
.style(move |_: &Theme, st| button::Style {
|
.style(move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (is_sel, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, _) => ACTIVE,
|
let pair = match (is_sel, st) {
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
(true, _) => Some(palette.primary.strong),
|
||||||
r: 0.26,
|
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||||
g: 0.26,
|
Some(palette.background.strong)
|
||||||
b: 0.26,
|
}
|
||||||
a: 1.0,
|
_ => None,
|
||||||
},
|
};
|
||||||
_ => Color::TRANSPARENT,
|
button::Style {
|
||||||
})),
|
background: pair.map(|p| Background::Color(p.color)),
|
||||||
text_color: TEXT,
|
text_color: pair
|
||||||
|
.map(|p| p.text)
|
||||||
|
.unwrap_or(palette.background.base.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([2, 8])
|
.padding([2, 8])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -221,16 +177,19 @@ pub fn view_window<'a>(
|
||||||
|
|
||||||
let aci_list = container(
|
let aci_list = container(
|
||||||
column![
|
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))
|
container(scrollable(column(aci_items).spacing(1)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into()
|
radius: 3.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
@ -269,12 +228,12 @@ pub fn view_window<'a>(
|
||||||
.map(|e| format!("{}%", e.screening))
|
.map(|e| format!("{}%", e.screening))
|
||||||
.unwrap_or_else(|| "—".into());
|
.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(
|
let edit_panel = container(
|
||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
text("ACI:").size(11).color(DIM).width(100),
|
text("ACI:").size(11).style(muted_style).width(100),
|
||||||
text(format!("{selected_aci}")).size(11),
|
text(format!("{selected_aci}")).size(11),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
@ -298,7 +257,7 @@ pub fn view_window<'a>(
|
||||||
.size(11)
|
.size(11)
|
||||||
.padding([4, 8]),
|
.padding([4, 8]),
|
||||||
Space::new().height(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!(" Color: {cur_color}")).size(10),
|
||||||
text(format!(" Lineweight: {cur_lw}")).size(10),
|
text(format!(" Lineweight: {cur_lw}")).size(10),
|
||||||
text(format!(" Screening: {cur_scr}")).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);
|
let body = row![aci_list, vsep(), edit_panel].height(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
container(column![toolbar, hdivider(), body].spacing(0))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,7 @@
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, canvas, column, container, radio, row, text, text_input, Space};
|
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};
|
use iced::{mouse, Background, Border, 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 };
|
|
||||||
|
|
||||||
const CELL_PX: f32 = 44.0;
|
const CELL_PX: f32 = 44.0;
|
||||||
|
|
||||||
|
|
@ -37,22 +29,23 @@ impl canvas::Program<Message> for GlyphCanvas {
|
||||||
&self,
|
&self,
|
||||||
_state: &(),
|
_state: &(),
|
||||||
renderer: &iced::Renderer,
|
renderer: &iced::Renderer,
|
||||||
_theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) -> Vec<canvas::Geometry> {
|
||||||
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
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 (cx, cy) = (bounds.width * 0.5, bounds.height * 0.5);
|
||||||
let r = bounds.width.min(bounds.height) * 0.30;
|
let r = bounds.width.min(bounds.height) * 0.30;
|
||||||
let stroke = canvas::Stroke {
|
let stroke = canvas::Stroke {
|
||||||
width: 1.4,
|
width: 1.4,
|
||||||
style: canvas::Style::Solid(GLYPH),
|
style: canvas::Style::Solid(glyph),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let line = |a: Point, b: Point| canvas::Path::line(a, b);
|
let line = |a: Point, b: Point| canvas::Path::line(a, b);
|
||||||
|
|
||||||
match self.mode & 0x0F {
|
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 => {}
|
1 => {}
|
||||||
2 => {
|
2 => {
|
||||||
frame.stroke(&line(Point::new(cx - r, cy), Point::new(cx + r, cy)), stroke.clone());
|
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)
|
button(glyph)
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.on_press(Message::PointStyleSetMode(value))
|
.on_press(Message::PointStyleSetMode(value))
|
||||||
.style(move |_: &Theme, status| {
|
.style(move |theme: &Theme, status| {
|
||||||
let bg = if selected {
|
let palette = theme.extended_palette();
|
||||||
CELL_SEL
|
let pair = if selected {
|
||||||
|
palette.primary.strong
|
||||||
} else if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
} 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 {
|
} else {
|
||||||
CELL
|
palette.background.weak
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 },
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
|
text_color: pair.text,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.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 {
|
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 {
|
border: Border {
|
||||||
color: Color { r: 0.3, g: 0.3, b: 0.3, a: 1.0 },
|
color: border,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
icon: DIM,
|
icon: palette.background.base.text,
|
||||||
placeholder: DIM,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: WHITE,
|
value: palette.background.base.text,
|
||||||
selection: ACCENT,
|
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![
|
let size_row = row![
|
||||||
text("Point Size:").size(13).color(WHITE),
|
text("Point Size:").size(13),
|
||||||
Space::new().width(10),
|
Space::new().width(10),
|
||||||
text_input("0", size_buf)
|
text_input("0", size_buf)
|
||||||
.on_input(Message::PointStyleSizeInput)
|
.on_input(Message::PointStyleSizeInput)
|
||||||
|
|
@ -147,7 +153,7 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
||||||
.size(13)
|
.size(13)
|
||||||
.width(110),
|
.width(110),
|
||||||
Space::new().width(6),
|
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);
|
.align_y(iced::Center);
|
||||||
|
|
||||||
|
|
@ -171,26 +177,14 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
||||||
]
|
]
|
||||||
.spacing(6);
|
.spacing(6);
|
||||||
|
|
||||||
let ok = button(text("OK").size(13).color(WHITE))
|
let ok = button(text("OK").size(13))
|
||||||
.padding([5, 22])
|
.padding([5, 22])
|
||||||
.on_press(Message::PointStyleOk)
|
.on_press(Message::PointStyleOk)
|
||||||
.style(|_: &Theme, status| {
|
.style(button::primary);
|
||||||
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()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
container(
|
container(
|
||||||
column![
|
column![
|
||||||
text("Point Style").size(18).color(WHITE),
|
text("Point Style").size(18),
|
||||||
Space::new().height(6),
|
Space::new().height(6),
|
||||||
grid,
|
grid,
|
||||||
Space::new().height(12),
|
Space::new().height(12),
|
||||||
|
|
@ -203,8 +197,10 @@ pub fn view_window<'a>(pdmode: i16, relative: bool, size_buf: &str) -> Element<'
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.padding(20),
|
.padding(20),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -6,53 +6,36 @@
|
||||||
//! messages instead of the StyleKind machinery.
|
//! messages instead of the StyleKind machinery.
|
||||||
|
|
||||||
use crate::app::Message;
|
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::{
|
use iced::widget::{
|
||||||
column, container, mouse_area, row, scrollable, text, text_input, Space,
|
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.
|
/// Inline-rename text-input id, so the rename-start handler can focus it.
|
||||||
pub fn rename_input_id() -> iced::widget::Id {
|
pub fn rename_input_id() -> iced::widget::Id {
|
||||||
iced::widget::Id::new("scale-rename-input")
|
iced::widget::Id::new("scale-rename-input")
|
||||||
}
|
}
|
||||||
|
|
||||||
const INPUT_BG: Color = Color {
|
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||||
r: 0.10,
|
let palette = theme.extended_palette();
|
||||||
g: 0.10,
|
let border = match status {
|
||||||
b: 0.10,
|
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||||
a: 1.0,
|
_ => palette.background.neutral.color,
|
||||||
};
|
};
|
||||||
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 {
|
|
||||||
text_input::Style {
|
text_input::Style {
|
||||||
background: Background::Color(INPUT_BG),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: border,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
icon: TEXT,
|
icon: palette.background.base.text,
|
||||||
placeholder: DIM,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: TEXT,
|
value: palette.background.base.text,
|
||||||
selection: Color {
|
selection: palette.primary.base.color.scale_alpha(0.5),
|
||||||
r: 0.20,
|
|
||||||
g: 0.46,
|
|
||||||
b: 0.80,
|
|
||||||
a: 0.45,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,8 +63,10 @@ pub fn view_window<'a, 'b>(
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -105,21 +90,24 @@ pub fn view_window<'a, 'b>(
|
||||||
}
|
}
|
||||||
let is_sel = name.as_str() == selected;
|
let is_sel = name.as_str() == selected;
|
||||||
let is_cur = name.eq_ignore_ascii_case(current);
|
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![
|
let label = row![
|
||||||
check,
|
check,
|
||||||
text(name.clone()).size(11).color(TEXT).width(Fill),
|
text(name.clone()).size(11).width(Fill),
|
||||||
text(ratio.clone()).size(10).color(DIM),
|
text(ratio.clone()).size(10).style(muted_text_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
let cell = container(label)
|
let cell = container(label)
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: is_sel.then_some(Background::Color(ACTIVE)),
|
let pair = theme.extended_palette().primary.strong;
|
||||||
text_color: Some(TEXT),
|
container::Style {
|
||||||
|
background: is_sel.then_some(Background::Color(pair.color)),
|
||||||
|
text_color: is_sel.then_some(pair.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mouse_area(cell)
|
mouse_area(cell)
|
||||||
.on_press(Message::ScaleManagerSelect(name.clone()))
|
.on_press(Message::ScaleManagerSelect(name.clone()))
|
||||||
|
|
@ -130,16 +118,19 @@ pub fn view_window<'a, 'b>(
|
||||||
|
|
||||||
let list_panel = container(
|
let list_panel = container(
|
||||||
column![
|
column![
|
||||||
text("Scales").size(10).color(DIM),
|
text("Scales").size(10).style(muted_text_style),
|
||||||
container(scrollable(column(rows).spacing(1)).height(Fill))
|
container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into()
|
radius: 3.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
@ -161,7 +152,7 @@ pub fn view_window<'a, 'b>(
|
||||||
let field =
|
let field =
|
||||||
|label: &'static str, ph: &'static str, value: &str, on: fn(String) -> Message| {
|
|label: &'static str, ph: &'static str, value: &str, on: fn(String) -> Message| {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(96),
|
text(label).size(11).style(muted_text_style).width(96),
|
||||||
text_input(ph, value)
|
text_input(ph, value)
|
||||||
.on_input(on)
|
.on_input(on)
|
||||||
.style(field_style)
|
.style(field_style)
|
||||||
|
|
@ -175,13 +166,13 @@ pub fn view_window<'a, 'b>(
|
||||||
|
|
||||||
let editor = container(
|
let editor = container(
|
||||||
column![
|
column![
|
||||||
text("Scale").size(10).color(DIM),
|
text("Scale").size(10).style(muted_text_style),
|
||||||
field("Paper units", "1", paper_buf, Message::ScaleManagerPaperBuf),
|
field("Paper units", "1", paper_buf, Message::ScaleManagerPaperBuf),
|
||||||
field("Drawing units", "50", drawing_buf, Message::ScaleManagerDrawingBuf),
|
field("Drawing units", "50", drawing_buf, Message::ScaleManagerDrawingBuf),
|
||||||
Space::new().height(6),
|
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.")
|
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)
|
.size(10)
|
||||||
.color(DIM),
|
.style(muted_text_style),
|
||||||
]
|
]
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
)
|
)
|
||||||
|
|
@ -192,8 +183,10 @@ pub fn view_window<'a, 'b>(
|
||||||
let body = row![list_panel, vsep(), editor].height(Fill);
|
let body = row![list_panel, vsep(), editor].height(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body])
|
container(column![toolbar, hdivider(), body])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
use crate::app::{Message, StyleKind};
|
use crate::app::{Message, StyleKind};
|
||||||
use iced::widget::{container, mouse_area, row, text, text_input};
|
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
|
/// Shared id for the inline rename field, so the rename-start handler can focus
|
||||||
/// it the moment the row turns editable.
|
/// 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")
|
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
|
/// 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
|
/// the style being renamed (`rename_active`), otherwise a selectable row whose
|
||||||
/// double click starts the rename. The current style gets a green ✓.
|
/// double click starts the rename. The current style gets a green ✓.
|
||||||
|
|
@ -61,16 +42,19 @@ pub fn item<'a>(
|
||||||
} else {
|
} else {
|
||||||
// Fixed-width ✓ column keeps every name left-aligned whether or not the
|
// Fixed-width ✓ column keeps every name left-aligned whether or not the
|
||||||
// row is current.
|
// row is current.
|
||||||
let check = crate::ui::icons::check_cell(is_current, CURRENT_CHECK);
|
let check = crate::ui::icons::themed_check_cell(is_current);
|
||||||
let label = row![check, text(name.to_string()).size(11).color(TEXT)]
|
let label = row![check, text(name.to_string()).size(11)]
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
let cell = container(label)
|
let cell = container(label)
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: is_selected.then_some(Background::Color(ACTIVE)),
|
let pair = theme.extended_palette().primary.strong;
|
||||||
text_color: Some(TEXT),
|
container::Style {
|
||||||
|
background: is_selected.then_some(Background::Color(pair.color)),
|
||||||
|
text_color: is_selected.then_some(pair.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mouse_area(cell)
|
mouse_area(cell)
|
||||||
.on_press(on_select)
|
.on_press(on_select)
|
||||||
|
|
|
||||||
|
|
@ -10,52 +10,7 @@
|
||||||
use crate::app::{Message, StyleKind};
|
use crate::app::{Message, StyleKind};
|
||||||
use iced::widget::button::{Status, Style};
|
use iced::widget::button::{Status, Style};
|
||||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
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};
|
||||||
|
|
||||||
// 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Everything the shared frame needs. The per-manager `editor` element is the
|
/// Everything the shared frame needs. The per-manager `editor` element is the
|
||||||
/// only bespoke part.
|
/// only bespoke part.
|
||||||
|
|
@ -103,8 +58,10 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
let toolbar = container(bar)
|
let toolbar = container(bar)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -131,16 +88,19 @@ pub fn view<'a, 'b>(s: Scaffold<'a, 'b>) -> Element<'a, Message> {
|
||||||
|
|
||||||
let list_panel = container(
|
let list_panel = container(
|
||||||
column![
|
column![
|
||||||
text("Styles").size(10).color(DIM),
|
text("Styles").size(10).style(muted_text_style),
|
||||||
container(scrollable(column(rows).spacing(1)).height(Fill))
|
container(scrollable(column(rows).spacing(1)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into()
|
radius: 3.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(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);
|
let body = row![list_panel, vsep(), s.editor].height(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body])
|
container(column![toolbar, hdivider(), body])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.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 {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, Status) -> Style {
|
||||||
move |_: &Theme, st| Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (accent, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, Status::Hovered | Status::Pressed) => Color {
|
let pair = match (accent, st) {
|
||||||
r: 0.20,
|
(true, Status::Hovered | Status::Pressed) => palette.primary.strong,
|
||||||
g: 0.42,
|
(false, Status::Hovered | Status::Pressed) => palette.background.strong,
|
||||||
b: 0.72,
|
(true, _) => palette.primary.base,
|
||||||
a: 1.0,
|
_ => palette.background.weak,
|
||||||
},
|
};
|
||||||
(false, Status::Hovered | Status::Pressed) => Color {
|
Style {
|
||||||
r: 0.28,
|
background: Some(Background::Color(pair.color)),
|
||||||
g: 0.28,
|
text_color: pair.text,
|
||||||
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 {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..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))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -229,8 +188,10 @@ pub(crate) fn vsep<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(1).height(Fill))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -4,63 +4,41 @@ use crate::app::Message;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Column,
|
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Column,
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
use iced::{Background, Border, 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (accent, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
let pair = match (accent, st) {
|
||||||
r: 0.20,
|
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||||
g: 0.42,
|
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||||
b: 0.72,
|
palette.background.strong
|
||||||
a: 1.0,
|
}
|
||||||
},
|
(true, _) => palette.primary.base,
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
_ => palette.background.weak,
|
||||||
r: 0.28,
|
};
|
||||||
g: 0.28,
|
button::Style {
|
||||||
b: 0.28,
|
background: Some(Background::Color(pair.color)),
|
||||||
a: 1.0,
|
text_color: pair.text,
|
||||||
},
|
|
||||||
(true, _) => ACCENT,
|
|
||||||
_ => Color {
|
|
||||||
r: 0.22,
|
|
||||||
g: 0.22,
|
|
||||||
b: 0.22,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
text_color: TEXT,
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..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 ──────────────────────────────────────────────
|
// ── Right: Details panel ──────────────────────────────────────────────
|
||||||
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
let info_row = |label: &'static str, val: String| -> Element<'_, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(160),
|
text(label).size(11).style(muted_style).width(160),
|
||||||
text(val).size(11),
|
text(val).size(11),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
@ -108,7 +86,7 @@ pub fn view_window<'a>(
|
||||||
field: &'static str|
|
field: &'static str|
|
||||||
-> Element<'a, Message> {
|
-> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(150),
|
text(label).size(11).style(muted_style).width(150),
|
||||||
text_input(placeholder, value)
|
text_input(placeholder, value)
|
||||||
.on_input(move |v| Message::TableStyleCellEdit {
|
.on_input(move |v| Message::TableStyleCellEdit {
|
||||||
row,
|
row,
|
||||||
|
|
@ -142,14 +120,14 @@ pub fn view_window<'a>(
|
||||||
Message::TableColorMore(row, field),
|
Message::TableColorMore(row, field),
|
||||||
Message::OpenColorWindow(crate::app::ColorPickTarget::Table(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)
|
.spacing(8)
|
||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
let mut col = Column::new()
|
let mut col = Column::new()
|
||||||
.spacing(3)
|
.spacing(3)
|
||||||
.push(text(row_label).size(11).color(ACCENT))
|
.push(text(row_label).size(11).style(primary_style))
|
||||||
.push(cell_in(
|
.push(cell_in(
|
||||||
" Text style:",
|
" Text style:",
|
||||||
"Standard",
|
"Standard",
|
||||||
|
|
@ -161,7 +139,7 @@ pub fn view_window<'a>(
|
||||||
.push(cell_color(" Fill color:", &cell_fillcolor[r], "fillcolor"))
|
.push(cell_color(" Fill color:", &cell_fillcolor[r], "fillcolor"))
|
||||||
.push(
|
.push(
|
||||||
row![
|
row![
|
||||||
text(" Alignment:").size(11).color(DIM).width(150),
|
text(" Alignment:").size(11).style(muted_style).width(150),
|
||||||
pick_list(
|
pick_list(
|
||||||
[
|
[
|
||||||
"TopLeft",
|
"TopLeft",
|
||||||
|
|
@ -199,7 +177,7 @@ pub fn view_window<'a>(
|
||||||
.push(
|
.push(
|
||||||
text(" Borders (type / weight / color / spacing / hidden)")
|
text(" Borders (type / weight / color / spacing / hidden)")
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(DIM),
|
.style(muted_style),
|
||||||
);
|
);
|
||||||
|
|
||||||
let borders: [(&'static str, &acadrust::objects::TableCellBorder); 6] = [
|
let borders: [(&'static str, &acadrust::objects::TableCellBorder); 6] = [
|
||||||
|
|
@ -214,7 +192,7 @@ pub fn view_window<'a>(
|
||||||
let bu = b as u8;
|
let bu = b as u8;
|
||||||
col = col.push(
|
col = col.push(
|
||||||
row![
|
row![
|
||||||
text(format!(" {bname}")).size(11).color(DIM).width(28),
|
text(format!(" {bname}")).size(11).style(muted_style).width(28),
|
||||||
pick_list(
|
pick_list(
|
||||||
["Single", "Double"]
|
["Single", "Double"]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -282,7 +260,7 @@ pub fn view_window<'a>(
|
||||||
column![
|
column![
|
||||||
info_row("Name:", s.name.clone()),
|
info_row("Name:", s.name.clone()),
|
||||||
row![
|
row![
|
||||||
text("Description:").size(11).color(DIM).width(160),
|
text("Description:").size(11).style(muted_style).width(160),
|
||||||
text_input("", description_buf)
|
text_input("", description_buf)
|
||||||
.on_input(|v| Message::TableStyleEdit {
|
.on_input(|v| Message::TableStyleEdit {
|
||||||
field: "description",
|
field: "description",
|
||||||
|
|
@ -294,7 +272,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
row![
|
row![
|
||||||
text("Flow direction:").size(11).color(DIM).width(160),
|
text("Flow direction:").size(11).style(muted_style).width(160),
|
||||||
pick_list(
|
pick_list(
|
||||||
["Down", "Up"]
|
["Down", "Up"]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -314,7 +292,7 @@ pub fn view_window<'a>(
|
||||||
.size(14)
|
.size(14)
|
||||||
.text_size(11),
|
.text_size(11),
|
||||||
row![
|
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)
|
text_input("1.5", hmargin_buf)
|
||||||
.on_input(|v| Message::TableStyleEdit {
|
.on_input(|v| Message::TableStyleEdit {
|
||||||
field: "hmargin",
|
field: "hmargin",
|
||||||
|
|
@ -326,7 +304,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
row![
|
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)
|
text_input("1.5", vmargin_buf)
|
||||||
.on_input(|v| Message::TableStyleEdit {
|
.on_input(|v| Message::TableStyleEdit {
|
||||||
field: "vmargin",
|
field: "vmargin",
|
||||||
|
|
@ -362,7 +340,7 @@ pub fn view_window<'a>(
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} 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])
|
.padding([12, 12])
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::app::StyleKind;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, canvas, checkbox, column, container, row, scrollable, text, text_input, Space,
|
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.
|
/// View-model for the Text Style editor window.
|
||||||
pub struct TextStyleView<'a> {
|
pub struct TextStyleView<'a> {
|
||||||
|
|
@ -28,49 +28,6 @@ pub struct TextStyleView<'a> {
|
||||||
pub rename_buf: &'a str,
|
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] = &[
|
const BUILTIN_FONTS: &[&str] = &[
|
||||||
"Standard", "ISO", "Simplex", "RomanS", "RomanD", "RomanC", "RomanT", "ItalicC", "ItalicT",
|
"Standard", "ISO", "Simplex", "RomanS", "RomanD", "RomanC", "RomanT", "ItalicC", "ItalicT",
|
||||||
"ScriptS", "ScriptC", "GothGBT", "GothGRT", "GothITT", "GreekC", "Symbol",
|
"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 {
|
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (active, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, _) => ACTIVE,
|
let pair = match (active, st) {
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
(true, _) => Some(palette.primary.strong),
|
||||||
r: 0.26,
|
(false, button::Status::Hovered | button::Status::Pressed) => {
|
||||||
g: 0.26,
|
Some(palette.background.strong)
|
||||||
b: 0.26,
|
}
|
||||||
a: 1.0,
|
_ => None,
|
||||||
},
|
};
|
||||||
_ => Color::TRANSPARENT,
|
button::Style {
|
||||||
})),
|
background: pair.map(|p| Background::Color(p.color)),
|
||||||
text_color: TEXT,
|
text_color: pair.map(|p| p.text).unwrap_or(palette.background.base.text),
|
||||||
..Default::default()
|
..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 {
|
text_input::Style {
|
||||||
background: Background::Color(FIELD),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: border,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
icon: TEXT,
|
icon: palette.background.base.text,
|
||||||
placeholder: DIM,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: TEXT,
|
value: palette.background.base.text,
|
||||||
selection: ACCENT,
|
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))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -143,7 +120,7 @@ impl canvas::Program<Message> for TextPreviewCanvas {
|
||||||
&self,
|
&self,
|
||||||
_state: &(),
|
_state: &(),
|
||||||
renderer: &iced::Renderer,
|
renderer: &iced::Renderer,
|
||||||
_theme: &Theme,
|
theme: &Theme,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<canvas::Geometry> {
|
) -> Vec<canvas::Geometry> {
|
||||||
|
|
@ -185,7 +162,7 @@ impl canvas::Program<Message> for TextPreviewCanvas {
|
||||||
};
|
};
|
||||||
let stroke = canvas::Stroke {
|
let stroke = canvas::Stroke {
|
||||||
width: 1.4,
|
width: 1.4,
|
||||||
style: canvas::Style::Solid(TEXT),
|
style: canvas::Style::Solid(theme.extended_palette().background.base.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
for s in &strokes {
|
for s in &strokes {
|
||||||
|
|
@ -237,16 +214,19 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
|
|
||||||
let font_panel = container(
|
let font_panel = container(
|
||||||
column![
|
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))
|
container(scrollable(column(font_items).spacing(1)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into()
|
radius: 3.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
@ -275,7 +255,7 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
field: &'static str,
|
field: &'static str,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(120),
|
text(label).size(11).style(muted_style).width(120),
|
||||||
text_input(ph, buf)
|
text_input(ph, buf)
|
||||||
.on_input(move |v| Message::TextStyleEdit { field, value: v })
|
.on_input(move |v| Message::TextStyleEdit { field, value: v })
|
||||||
.style(field_style)
|
.style(field_style)
|
||||||
|
|
@ -312,7 +292,7 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
// ── Right: Properties ─────────────────────────────────────────────────
|
// ── Right: Properties ─────────────────────────────────────────────────
|
||||||
let props_panel = container(
|
let props_panel = container(
|
||||||
column![
|
column![
|
||||||
text("Properties").size(11).color(ACCENT),
|
text("Properties").size(11).style(primary_style),
|
||||||
frow("Big Font:", "big-font file…", bigfont_buf, "bigfont"),
|
frow("Big Font:", "big-font file…", bigfont_buf, "bigfont"),
|
||||||
frow("TrueType Font:", "e.g. Arial", ttf_buf, "ttf"),
|
frow("TrueType Font:", "e.g. Arial", ttf_buf, "ttf"),
|
||||||
frow("Fixed Height:", "0 = variable", height_buf, "height"),
|
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)
|
.size(15)
|
||||||
.text_size(11),
|
.text_size(11),
|
||||||
Space::new().height(8),
|
Space::new().height(8),
|
||||||
text("Preview").size(10).color(DIM),
|
text("Preview").size(10).style(muted_style),
|
||||||
container(preview)
|
container(preview)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(FIELD)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.base.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 4.0.into()
|
radius: 4.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
|
|
@ -382,16 +365,19 @@ pub fn view_window<'a>(v: TextStyleView<'a>) -> Element<'a, Message> {
|
||||||
|
|
||||||
let ttf_panel = container(
|
let ttf_panel = container(
|
||||||
column![
|
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))
|
container(scrollable(column(ttf_items).spacing(1)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into()
|
radius: 3.0.into()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,16 @@
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, row, text};
|
use iced::widget::{button, column, container, row, text};
|
||||||
use iced::{Background, Border, Color, Element, Theme};
|
use iced::{Background, 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
|
fn info_row<'a>(label: &'static str, value: String) -> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(100),
|
text(label)
|
||||||
text(value).size(11).color(WHITE),
|
.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)
|
.spacing(8)
|
||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
|
|
@ -57,10 +25,16 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
||||||
|
|
||||||
let logo = container(
|
let logo = container(
|
||||||
column![
|
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")
|
text("CAD application for Architecture & Engineering")
|
||||||
.size(11)
|
.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)
|
.spacing(4)
|
||||||
.align_x(iced::Center),
|
.align_x(iced::Center),
|
||||||
|
|
@ -82,35 +56,11 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
||||||
.spacing(2)
|
.spacing(2)
|
||||||
.padding([12, 16]),
|
.padding([12, 16]),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box);
|
||||||
background: Some(Background::Color(PANEL)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let copy_btn = button(text("Copy Info").size(11))
|
let copy_btn = button(text("Copy Info").size(11))
|
||||||
.on_press(Message::AboutCopyInfo)
|
.on_press(Message::AboutCopyInfo)
|
||||||
.style(|_: &Theme, st| button::Style {
|
.style(button::primary)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([6, 16]);
|
.padding([6, 16]);
|
||||||
|
|
||||||
let footer = row![copy_btn]
|
let footer = row![copy_btn]
|
||||||
|
|
@ -132,8 +82,10 @@ pub fn view_window<'a>() -> Element<'a, Message> {
|
||||||
left: 20.0,
|
left: 20.0,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
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.
|
/// Which column of an alias row a text edit targets.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|
@ -18,33 +18,21 @@ pub enum AliasField {
|
||||||
/// Right-hand lane reserved for the scrollbar so it never overlaps the ✕ column.
|
/// Right-hand lane reserved for the scrollbar so it never overlaps the ✕ column.
|
||||||
const GUTTER: f32 = 16.0;
|
const GUTTER: f32 = 16.0;
|
||||||
|
|
||||||
const BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
const FIELD_BG: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 };
|
iced::widget::text::Style {
|
||||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
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 },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the alias editor content. `rows` is the live working buffer.
|
/// Build the alias editor content. `rows` is the live working buffer.
|
||||||
pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
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(
|
let hint = text(
|
||||||
"Type an alias and the command it runs (e.g. L → LINE). \
|
"Type an alias and the command it runs (e.g. L → LINE). \
|
||||||
Apply to save to ocad.pgp; closing discards unapplied edits.",
|
Apply to save to ocad.pgp; closing discards unapplied edits.",
|
||||||
)
|
)
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(DIM);
|
.style(muted_style);
|
||||||
|
|
||||||
// Right gutter reserved so the scrollbar has its own lane and never sits on
|
// 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
|
// 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(
|
let head = container(
|
||||||
row![
|
row![
|
||||||
container(text("Alias").size(11).color(DIM)).width(Length::Fixed(120.0)),
|
container(text("Alias").size(11).style(muted_style)).width(Length::Fixed(120.0)),
|
||||||
container(text("Command").size(11).color(DIM)).width(Fill),
|
container(text("Command").size(11).style(muted_style)).width(Fill),
|
||||||
Space::new().width(Length::Fixed(30.0)),
|
Space::new().width(Length::Fixed(30.0)),
|
||||||
]
|
]
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
|
|
@ -65,31 +53,18 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||||
for (idx, (alias, cmd)) in rows.iter().enumerate() {
|
for (idx, (alias, cmd)) in rows.iter().enumerate() {
|
||||||
let alias_box = text_input("alias", alias)
|
let alias_box = text_input("alias", alias)
|
||||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Alias, value: v })
|
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Alias, value: v })
|
||||||
.style(field_style)
|
|
||||||
.size(13)
|
.size(13)
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.width(Length::Fixed(120.0));
|
.width(Length::Fixed(120.0));
|
||||||
let cmd_box = text_input("command", cmd)
|
let cmd_box = text_input("command", cmd)
|
||||||
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Command, value: v })
|
.on_input(move |v| Message::AliasEditorInput { idx, field: AliasField::Command, value: v })
|
||||||
.style(field_style)
|
|
||||||
.size(13)
|
.size(13)
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.width(Fill);
|
.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))
|
.on_press(Message::AliasEditorRemove(idx))
|
||||||
.padding([2, 6])
|
.padding([2, 6])
|
||||||
.style(|_: &Theme, status| {
|
.style(button::danger);
|
||||||
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()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
list = list.push(
|
list = list.push(
|
||||||
row![alias_box, cmd_box, del]
|
row![alias_box, cmd_box, del]
|
||||||
.spacing(8)
|
.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)
|
.on_press(Message::AliasEditorAdd)
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.style(|_: &Theme, status| {
|
.style(button::secondary);
|
||||||
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()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply — primary action; commits the rows to ocad.pgp and stays open.
|
// 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)
|
.on_press(Message::AliasEditorApply)
|
||||||
.padding([4, 16])
|
.padding([4, 16])
|
||||||
.style(|_: &Theme, status| {
|
.style(button::primary);
|
||||||
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()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
container(
|
container(
|
||||||
column![
|
column![
|
||||||
|
|
@ -149,8 +100,10 @@ pub fn view_window(rows: &[(String, String)]) -> Element<'_, Message> {
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -19,23 +19,10 @@ use acadrust::types::{Color as AcadColor, LineWeight};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, checkbox, column, container, pick_list, row, scrollable, text, text_input, Space,
|
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};
|
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;
|
const LABEL_W: f32 = 120.0;
|
||||||
|
|
||||||
/// Which tab of the editor is showing.
|
/// 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 {
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
text_input::Style {
|
iced::widget::text::Style {
|
||||||
background: Background::Color(FIELD_BG),
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
|
||||||
icon: WHITE,
|
|
||||||
placeholder: DIM,
|
|
||||||
value: WHITE,
|
|
||||||
selection: ACCENT,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accent_btn(_t: &Theme, status: button::Status) -> button::Style {
|
fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style {
|
||||||
let bg = if matches!(status, button::Status::Hovered | button::Status::Pressed) {
|
let palette = theme.extended_palette();
|
||||||
Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 }
|
let border = match status {
|
||||||
} else {
|
text_input::Status::Focused { .. } => palette.primary.base.color,
|
||||||
ACCENT
|
_ => palette.background.neutral.color,
|
||||||
};
|
};
|
||||||
button::Style {
|
text_input::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Background::Color(palette.background.base.color),
|
||||||
text_color: WHITE,
|
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
icon: palette.background.base.text,
|
||||||
..Default::default()
|
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))
|
container(Space::new().width(Length::Fill).height(1))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -186,7 +172,7 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||||
/// One `label : widget` row with a fixed-width label column.
|
/// One `label : widget` row with a fixed-width label column.
|
||||||
fn field_row<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
|
fn field_row<'a>(label: &'a str, widget: Element<'a, Message>) -> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
container(text(label).size(12).color(DIM)).width(LABEL_W),
|
container(text(label).size(12).style(muted_style)).width(LABEL_W),
|
||||||
widget,
|
widget,
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
@ -226,20 +212,28 @@ fn pick_field<'a>(
|
||||||
|
|
||||||
fn tab_button<'a>(label: &'a str, this: AttrTab, active: AttrTab) -> Element<'a, Message> {
|
fn tab_button<'a>(label: &'a str, this: AttrTab, active: AttrTab) -> Element<'a, Message> {
|
||||||
let is_active = this == active;
|
let is_active = this == active;
|
||||||
button(text(label).size(11).color(WHITE))
|
button(text(label).size(11))
|
||||||
.padding([4, 12])
|
.padding([4, 12])
|
||||||
.on_press(Message::AttrEditorTab(this))
|
.on_press(Message::AttrEditorTab(this))
|
||||||
.style(move |_t: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (is_active, status) {
|
let palette = theme.extended_palette();
|
||||||
(true, _) => ACTIVE,
|
let pair = match (is_active, status) {
|
||||||
|
(true, _) => palette.primary.strong,
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
(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 },
|
_ => palette.background.weak,
|
||||||
})),
|
};
|
||||||
text_color: WHITE,
|
button::Style {
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
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()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -259,20 +253,22 @@ pub fn view_window<'a>(
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
// ── Top toolbar: block name on the left, Apply on the right ───────────
|
// ── Top toolbar: block name on the left, Apply on the right ───────────
|
||||||
// Mirrors the style-manager windows (actions left, primary action 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])
|
.padding([4, 14])
|
||||||
.on_press(Message::AttrEditorApply)
|
.on_press(Message::AttrEditorApply)
|
||||||
.style(accent_btn);
|
.style(button::primary);
|
||||||
let toolbar = container(
|
let toolbar = container(
|
||||||
row![
|
row![
|
||||||
text(format!("Block: {block}")).size(12).color(DIM),
|
text(format!("Block: {block}")).size(12).style(muted_style),
|
||||||
Space::new().width(Length::Fill),
|
Space::new().width(Length::Fill),
|
||||||
apply,
|
apply,
|
||||||
]
|
]
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -286,7 +282,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(2);
|
.spacing(2);
|
||||||
|
|
||||||
let body: Element<'_, Message> = if rows.is_empty() {
|
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 {
|
} else {
|
||||||
match tab {
|
match tab {
|
||||||
AttrTab::Attribute => attribute_tab(rows, selected),
|
AttrTab::Attribute => attribute_tab(rows, selected),
|
||||||
|
|
@ -303,8 +299,10 @@ pub fn view_window<'a>(
|
||||||
.padding(12);
|
.padding(12);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), content])
|
container(column![toolbar, hdivider(), content])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.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.
|
/// 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> {
|
fn attribute_tab<'a>(rows: &'a [AttrRow], selected: usize) -> Element<'a, Message> {
|
||||||
let head = row![
|
let head = row![
|
||||||
container(text("Tag").size(11).color(DIM)).width(130),
|
container(text("Tag").size(11).style(muted_style)).width(130),
|
||||||
container(text("Prompt").size(11).color(DIM)).width(Length::Fill),
|
container(text("Prompt").size(11).style(muted_style)).width(Length::Fill),
|
||||||
container(text("Value").size(11).color(DIM)).width(140),
|
container(text("Value").size(11).style(muted_style)).width(140),
|
||||||
]
|
]
|
||||||
.spacing(6);
|
.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() {
|
for (idx, r) in rows.iter().enumerate() {
|
||||||
let is_sel = idx == selected;
|
let is_sel = idx == selected;
|
||||||
let line = row![
|
let line = row![
|
||||||
container(text(r.tag.as_str()).size(12).color(WHITE)).width(130),
|
container(text(r.tag.as_str()).size(12)).width(130),
|
||||||
container(text(r.prompt.as_str()).size(12).color(DIM)).width(Length::Fill),
|
container(text(r.prompt.as_str()).size(12).style(muted_style)).width(Length::Fill),
|
||||||
container(text(r.value.as_str()).size(12).color(WHITE)).width(140),
|
container(text(r.value.as_str()).size(12)).width(140),
|
||||||
]
|
]
|
||||||
.spacing(6);
|
.spacing(6);
|
||||||
let btn = button(line)
|
let btn = button(line)
|
||||||
.on_press(Message::AttrEditorSelect(idx))
|
.on_press(Message::AttrEditorSelect(idx))
|
||||||
.padding([3, 4])
|
.padding([3, 4])
|
||||||
.width(Length::Fill)
|
.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 hovered = matches!(status, button::Status::Hovered);
|
||||||
let bg = if is_sel {
|
let pair = if is_sel {
|
||||||
ROW_SEL
|
palette.primary.strong
|
||||||
} else if hovered {
|
} else if hovered {
|
||||||
Color { r: 0.18, g: 0.18, b: 0.18, a: 1.0 }
|
palette.background.strong
|
||||||
} else {
|
} else {
|
||||||
ROW_BG
|
palette.background.weak
|
||||||
};
|
};
|
||||||
button::Style {
|
button::Style {
|
||||||
background: Some(Background::Color(bg)),
|
background: Some(Background::Color(pair.color)),
|
||||||
text_color: WHITE,
|
text_color: pair.text,
|
||||||
border: Border::default(),
|
border: Border::default(),
|
||||||
..Default::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).
|
/// scrolled into view after it is added (#271).
|
||||||
pub const LAYER_TABLE_SCROLL_ID: &str = "layer-manager-table-scroll";
|
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 ────────────────────────────────────────────────────────────
|
// ── Layer data ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -313,39 +342,15 @@ impl LayerPanel {
|
||||||
.size(FONT_SZ)
|
.size(FONT_SZ)
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.width(Length::Fixed(180.0))
|
.width(Length::Fixed(180.0))
|
||||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
.style(table_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,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
.spacing(2)
|
.spacing(2)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TOOLBAR_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -355,13 +360,15 @@ impl LayerPanel {
|
||||||
let sc = self.sort_col;
|
let sc = self.sort_col;
|
||||||
let sa = self.sort_asc;
|
let sa = self.sort_asc;
|
||||||
let mut header_row = row![
|
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),
|
sortable_header("Name", LayerSortCol::Name, Length::Fixed(name_col_w), sc, sa),
|
||||||
// Draggable divider: adjusts the Name column width (#359).
|
// Draggable divider: adjusts the Name column width (#359).
|
||||||
iced::widget::mouse_area(
|
iced::widget::mouse_area(
|
||||||
container(iced::widget::Space::new().width(2).height(14)).style(
|
container(iced::widget::Space::new().width(2).height(14)).style(
|
||||||
|_: &Theme| container::Style {
|
|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER_COLOR)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -390,20 +397,23 @@ impl LayerPanel {
|
||||||
header_row = header_row.push(
|
header_row = header_row.push(
|
||||||
text(vp.label.as_str())
|
text(vp.label.as_str())
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(DIM)
|
.style(muted_style)
|
||||||
.width(Length::Fixed(COL_ICON)),
|
.width(Length::Fixed(COL_ICON)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let col_header = container(header_row)
|
let col_header = container(header_row)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(COL_HEADER_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 0.0.into(),
|
radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
@ -451,8 +461,10 @@ impl LayerPanel {
|
||||||
|
|
||||||
// ── Full-window frame ─────────────────────────────────────────────
|
// ── Full-window frame ─────────────────────────────────────────────
|
||||||
container(column![toolbar, col_header, table].spacing(0))
|
container(column![toolbar, col_header, table].spacing(0))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -478,28 +490,26 @@ fn sortable_header<'a>(
|
||||||
active: Option<LayerSortCol>,
|
active: Option<LayerSortCol>,
|
||||||
asc: bool,
|
asc: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> 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)
|
.spacing(3)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
if active == Some(col) {
|
if active == Some(col) {
|
||||||
content = content.push(if asc {
|
content = content.push(if asc {
|
||||||
crate::ui::icons::arrow_up(8.0, DIM)
|
crate::ui::icons::themed_arrow_up(8.0)
|
||||||
} else {
|
} else {
|
||||||
crate::ui::icons::arrow_down(8.0, DIM)
|
crate::ui::icons::themed_arrow_down(8.0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
button(content)
|
button(content)
|
||||||
.on_press(Message::LayerSort(col))
|
.on_press(Message::LayerSort(col))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: matches!(
|
||||||
button::Status::Hovered | button::Status::Pressed => Color {
|
status,
|
||||||
r: 0.30,
|
button::Status::Hovered | button::Status::Pressed
|
||||||
g: 0.30,
|
)
|
||||||
b: 0.30,
|
.then_some(Background::Color(
|
||||||
a: 1.0,
|
theme.extended_palette().background.strong.color
|
||||||
},
|
)),
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding(Padding {
|
.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> {
|
fn toolbar_btn<'a>(icon: &'static [u8], label: &'a str, msg: Message) -> Element<'a, Message> {
|
||||||
button(
|
button(
|
||||||
row![
|
row![
|
||||||
crate::ui::icons::tinted(icon, 12.0, Color::WHITE),
|
crate::ui::icons::themed(icon, 12.0),
|
||||||
text(label).size(11).color(Color::WHITE),
|
text(label).size(11),
|
||||||
]
|
]
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered => Color {
|
let pair = match status {
|
||||||
r: 0.32,
|
button::Status::Hovered | button::Status::Pressed => {
|
||||||
g: 0.32,
|
palette.background.strong
|
||||||
b: 0.32,
|
}
|
||||||
a: 1.0,
|
_ => palette.background.weak,
|
||||||
},
|
};
|
||||||
button::Status::Pressed => Color {
|
button::Style {
|
||||||
r: 0.25,
|
background: Some(Background::Color(pair.color)),
|
||||||
g: 0.25,
|
|
||||||
b: 0.25,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color {
|
|
||||||
r: 0.26,
|
|
||||||
g: 0.26,
|
|
||||||
b: 0.26,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
},
|
},
|
||||||
text_color: Color::WHITE,
|
text_color: pair.text,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 10])
|
.padding([4, 10])
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -563,46 +563,46 @@ fn toolbar_btn_cond<'a>(
|
||||||
msg: Message,
|
msg: Message,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> 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(
|
let mut b = button(
|
||||||
row![
|
row![
|
||||||
crate::ui::icons::tinted(icon, 12.0, fg),
|
if enabled {
|
||||||
text(label).size(11).color(fg),
|
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)
|
.spacing(5)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match status {
|
let palette = theme.extended_palette();
|
||||||
button::Status::Hovered => Color {
|
let pair = match status {
|
||||||
r: 0.32,
|
button::Status::Hovered if enabled => palette.background.strong,
|
||||||
g: 0.32,
|
_ => palette.background.weak,
|
||||||
b: 0.32,
|
};
|
||||||
a: 1.0,
|
button::Style {
|
||||||
},
|
background: Some(Background::Color(pair.color)),
|
||||||
_ => Color {
|
|
||||||
r: 0.26,
|
|
||||||
g: 0.26,
|
|
||||||
b: 0.26,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
},
|
},
|
||||||
text_color: Color::WHITE,
|
text_color: if enabled {
|
||||||
|
pair.text
|
||||||
|
} else {
|
||||||
|
pair.text.scale_alpha(0.42)
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 10]);
|
.padding([4, 10]);
|
||||||
if enabled {
|
if enabled {
|
||||||
|
|
@ -616,21 +616,25 @@ fn toolbar_btn_cond<'a>(
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
/// Hover popup showing a layer's full name when the cell truncates it.
|
/// Hover popup showing a layer's full name when the cell truncates it.
|
||||||
fn name_tip<'a>(name: &'a str) -> Element<'a, Message> {
|
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 {
|
.padding(Padding {
|
||||||
top: 3.0,
|
top: 3.0,
|
||||||
bottom: 3.0,
|
bottom: 3.0,
|
||||||
left: 7.0,
|
left: 7.0,
|
||||||
right: 7.0,
|
right: 7.0,
|
||||||
})
|
})
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(PANEL_BG)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.strong.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER_COLOR,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
|
text_color: Some(palette.background.strong.text),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -655,16 +659,10 @@ fn layer_row<'a>(
|
||||||
.height(ICON_SZ),
|
.height(ICON_SZ),
|
||||||
)
|
)
|
||||||
.on_press(on_press)
|
.on_press(on_press)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: matches!(status, button::Status::Hovered).then_some(
|
||||||
button::Status::Hovered => Color {
|
Background::Color(theme.extended_palette().background.strong.color)
|
||||||
r: 0.35,
|
),
|
||||||
g: 0.35,
|
|
||||||
b: 0.35,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
|
|
@ -682,27 +680,9 @@ fn layer_row<'a>(
|
||||||
let lck_svg = crate::ui::icons::layer_lock(layer.locked);
|
let lck_svg = crate::ui::icons::layer_lock(layer.locked);
|
||||||
|
|
||||||
let status_dot: Element<'_, Message> = if is_current {
|
let status_dot: Element<'_, Message> = if is_current {
|
||||||
crate::ui::icons::tinted(
|
crate::ui::icons::themed_success(crate::ui::icons::CHECK, 13.0)
|
||||||
crate::ui::icons::CHECK,
|
|
||||||
13.0,
|
|
||||||
Color {
|
|
||||||
r: 0.25,
|
|
||||||
g: 0.85,
|
|
||||||
b: 0.45,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
crate::ui::icons::tinted(
|
crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 9.0)
|
||||||
crate::ui::icons::DOT,
|
|
||||||
9.0,
|
|
||||||
Color {
|
|
||||||
r: 0.55,
|
|
||||||
g: 0.55,
|
|
||||||
b: 0.55,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Name cell
|
// Name cell
|
||||||
|
|
@ -717,38 +697,7 @@ fn layer_row<'a>(
|
||||||
left: 4.0,
|
left: 4.0,
|
||||||
right: 4.0,
|
right: 4.0,
|
||||||
})
|
})
|
||||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
.style(table_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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.width(Length::Fixed(name_col_w))
|
.width(Length::Fixed(name_col_w))
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -756,20 +705,13 @@ fn layer_row<'a>(
|
||||||
let name_budget = ((name_col_w / 6.0) as usize).max(8);
|
let name_budget = ((name_col_w / 6.0) as usize).max(8);
|
||||||
let name_btn = button(
|
let name_btn = button(
|
||||||
text(crate::ui::text_util::elide(&layer.name, name_budget))
|
text(crate::ui::text_util::elide(&layer.name, name_budget))
|
||||||
.size(FONT_SZ)
|
.size(FONT_SZ),
|
||||||
.color(ROW_TEXT),
|
|
||||||
)
|
)
|
||||||
.on_press(Message::LayerRenameStart(index))
|
.on_press(Message::LayerRenameStart(index))
|
||||||
.style(|_: &Theme, status| button::Style {
|
.style(|theme: &Theme, status| button::Style {
|
||||||
background: Some(Background::Color(match status {
|
background: matches!(status, button::Status::Hovered).then_some(
|
||||||
button::Status::Hovered => Color {
|
Background::Color(theme.extended_palette().background.strong.color)
|
||||||
r: 0.30,
|
),
|
||||||
g: 0.30,
|
|
||||||
b: 0.30,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
|
|
@ -837,7 +779,7 @@ fn layer_row<'a>(
|
||||||
} else {
|
} else {
|
||||||
text(layer.linetype.as_str())
|
text(layer.linetype.as_str())
|
||||||
.size(FONT_SZ)
|
.size(FONT_SZ)
|
||||||
.color(DIM)
|
.style(muted_style)
|
||||||
.width(Length::Fixed(COL_LT))
|
.width(Length::Fixed(COL_LT))
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
@ -861,7 +803,7 @@ fn layer_row<'a>(
|
||||||
} else {
|
} else {
|
||||||
text(cur_lw_item.to_string())
|
text(cur_lw_item.to_string())
|
||||||
.size(FONT_SZ)
|
.size(FONT_SZ)
|
||||||
.color(DIM)
|
.style(muted_style)
|
||||||
.width(Length::Fixed(COL_LW))
|
.width(Length::Fixed(COL_LW))
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
@ -877,33 +819,13 @@ fn layer_row<'a>(
|
||||||
left: 4.0,
|
left: 4.0,
|
||||||
right: 4.0,
|
right: 4.0,
|
||||||
})
|
})
|
||||||
.style(|_: &Theme, _| iced::widget::text_input::Style {
|
.style(|theme: &Theme, status| {
|
||||||
background: iced::Background::Color(Color::TRANSPARENT),
|
let mut style = table_input_style(theme, status);
|
||||||
border: Border {
|
style.background = iced::Background::Color(Color::TRANSPARENT);
|
||||||
radius: 2.0.into(),
|
style
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.width(Length::Fixed(COL_TRANS));
|
.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![
|
let mut row_content = row![
|
||||||
container(status_dot)
|
container(status_dot)
|
||||||
.width(50)
|
.width(50)
|
||||||
|
|
@ -945,9 +867,20 @@ fn layer_row<'a>(
|
||||||
|
|
||||||
mouse_area(
|
mouse_area(
|
||||||
container(row_content)
|
container(row_content)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(bg)),
|
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()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
top: 0.0,
|
top: 0.0,
|
||||||
|
|
@ -965,31 +898,10 @@ fn layer_row<'a>(
|
||||||
// ── Combo style ────────────────────────────────────────────────────────────
|
// ── Combo style ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn combo_input_style(
|
fn combo_input_style(
|
||||||
_theme: &Theme,
|
theme: &Theme,
|
||||||
_status: iced::widget::text_input::Status,
|
status: iced::widget::text_input::Status,
|
||||||
) -> iced::widget::text_input::Style {
|
) -> iced::widget::text_input::Style {
|
||||||
iced::widget::text_input::Style {
|
table_input_style(theme, status)
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Display helpers ───────────────────────────────────────────────────────
|
// ── Display helpers ───────────────────────────────────────────────────────
|
||||||
|
|
@ -1054,67 +966,3 @@ const COL_COLOR: f32 = 90.0;
|
||||||
const COL_LT: f32 = 110.0;
|
const COL_LT: f32 = 110.0;
|
||||||
const COL_LW: f32 = 90.0;
|
const COL_LW: f32 = 90.0;
|
||||||
const COL_TRANS: f32 = 80.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 crate::app::Message;
|
||||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
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 {
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.13,
|
iced::widget::text::Style {
|
||||||
g: 0.13,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
b: 0.13,
|
}
|
||||||
a: 1.0,
|
}
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.15,
|
iced::widget::text::Style {
|
||||||
g: 0.15,
|
color: Some(theme.extended_palette().primary.base.color),
|
||||||
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 btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn btn_s(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (accent, st) {
|
if accent {
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => Color {
|
button::primary(theme, status)
|
||||||
r: 0.20,
|
} else {
|
||||||
g: 0.42,
|
button::secondary(theme, status)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
fn list_item(active: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match (active, st) {
|
if active {
|
||||||
(true, _) => ACTIVE,
|
button::primary(theme, status)
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => Color {
|
} else {
|
||||||
r: 0.26,
|
button::subtle(theme, status)
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,8 +40,10 @@ fn hdivider<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(Fill).height(1))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -145,8 +53,10 @@ fn vsep<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(1).height(Fill))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -169,34 +79,18 @@ pub fn view_window<'a>(
|
||||||
.padding([4, 10]),
|
.padding([4, 10]),
|
||||||
button(text("Delete").size(11))
|
button(text("Delete").size(11))
|
||||||
.on_press(Message::LayoutManagerDelete)
|
.on_press(Message::LayoutManagerDelete)
|
||||||
.style(move |_: &Theme, st| button::Style {
|
.style(move |theme: &Theme, status| {
|
||||||
background: Some(Background::Color(match st {
|
if is_model {
|
||||||
button::Status::Hovered | button::Status::Pressed => Color {
|
button::secondary(theme, status)
|
||||||
r: 0.60,
|
} else {
|
||||||
g: 0.20,
|
button::danger(theme, status)
|
||||||
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()
|
|
||||||
})
|
})
|
||||||
.padding([4, 10]),
|
.padding([4, 10]),
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
button(
|
button(
|
||||||
row![
|
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),
|
text("Move Left").size(11),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
|
|
@ -208,7 +102,7 @@ pub fn view_window<'a>(
|
||||||
button(
|
button(
|
||||||
row![
|
row![
|
||||||
text("Move Right").size(11),
|
text("Move Right").size(11),
|
||||||
crate::ui::icons::arrow_right(9.0, Color::WHITE),
|
crate::ui::icons::themed_arrow_right(9.0),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
@ -224,8 +118,10 @@ pub fn view_window<'a>(
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -241,11 +137,7 @@ pub fn view_window<'a>(
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
if is_cur {
|
if is_cur {
|
||||||
item_row = item_row.push(crate::ui::icons::tinted(
|
item_row = item_row.push(crate::ui::icons::themed_arrow_left(8.0));
|
||||||
crate::ui::icons::TRI_LEFT_B,
|
|
||||||
8.0,
|
|
||||||
Color::WHITE,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
button(item_row)
|
button(item_row)
|
||||||
.on_press(Message::LayoutManagerSelect(name.clone()))
|
.on_press(Message::LayoutManagerSelect(name.clone()))
|
||||||
|
|
@ -258,17 +150,9 @@ pub fn view_window<'a>(
|
||||||
|
|
||||||
let layout_list = container(
|
let layout_list = container(
|
||||||
column![
|
column![
|
||||||
text("Layouts").size(10).color(DIM),
|
text("Layouts").size(10).style(muted_style),
|
||||||
container(scrollable(column(list_items).spacing(2)).height(Fill))
|
container(scrollable(column(list_items).spacing(2)).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(LIST)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 3.0.into()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.padding(2),
|
.padding(2),
|
||||||
|
|
@ -293,38 +177,38 @@ pub fn view_window<'a>(
|
||||||
} else {
|
} else {
|
||||||
"Paper Space Layout"
|
"Paper Space Layout"
|
||||||
})
|
})
|
||||||
.size(13)
|
.size(13),
|
||||||
.color(TEXT),
|
|
||||||
Space::new().height(8),
|
Space::new().height(8),
|
||||||
row![
|
row![
|
||||||
text("Name:").size(11).color(DIM).width(80),
|
text("Name:").size(11).style(muted_style).width(80),
|
||||||
text(selected).size(11),
|
text(selected).size(11),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
row![
|
row![
|
||||||
text("Status:").size(11).color(DIM).width(80),
|
text("Status:").size(11).style(muted_style).width(80),
|
||||||
text(if selected == current.as_str() {
|
text(if selected == current.as_str() {
|
||||||
"Active"
|
"Active"
|
||||||
} else {
|
} else {
|
||||||
"Inactive"
|
"Inactive"
|
||||||
})
|
})
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(if selected == current.as_str() {
|
.style(move |theme: &Theme| {
|
||||||
ACCENT
|
if selected == current.as_str() {
|
||||||
} else {
|
primary_style(theme)
|
||||||
DIM
|
} else {
|
||||||
|
muted_style(theme)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
Space::new().height(16),
|
Space::new().height(16),
|
||||||
text("Rename").size(10).color(DIM),
|
text("Rename").size(10).style(muted_style),
|
||||||
row![
|
row![
|
||||||
text_input("New name…", rename_buf)
|
text_input("New name…", rename_buf)
|
||||||
.on_input(Message::LayoutManagerRenameBuf)
|
.on_input(Message::LayoutManagerRenameBuf)
|
||||||
.on_submit(Message::LayoutManagerRenameCommit)
|
.on_submit(Message::LayoutManagerRenameCommit)
|
||||||
.style(field_style)
|
|
||||||
.size(11)
|
.size(11)
|
||||||
.padding([4, 8]),
|
.padding([4, 8]),
|
||||||
button(text("OK").size(11))
|
button(text("OK").size(11))
|
||||||
|
|
@ -343,8 +227,10 @@ pub fn view_window<'a>(
|
||||||
let body = row![layout_list, vsep(), details].height(Fill);
|
let body = row![layout_list, vsep(), details].height(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
container(column![toolbar, hdivider(), body].spacing(0))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use iced::time::Instant;
|
use iced::time::Instant;
|
||||||
use iced::widget::{button, column, container, row, stack, text, Space};
|
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 std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
|
|
@ -60,13 +60,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||||
.width(Length::Fixed(fill_width))
|
.width(Length::Fixed(fill_width))
|
||||||
.height(Length::Fixed(BAR_TRACK_HEIGHT)),
|
.height(Length::Fixed(BAR_TRACK_HEIGHT)),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(Color {
|
background: Some(Background::Color(
|
||||||
r: 0.30,
|
theme.extended_palette().primary.base.color
|
||||||
g: 0.62,
|
)),
|
||||||
b: 0.95,
|
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -86,13 +83,10 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||||
let bar_track: Element<'_, Message> = container(
|
let bar_track: Element<'_, Message> = container(
|
||||||
stack![
|
stack![
|
||||||
container(Space::new().width(Length::Fixed(BAR_TRACK_WIDTH)).height(Length::Fixed(BAR_TRACK_HEIGHT)))
|
container(Space::new().width(Length::Fixed(BAR_TRACK_WIDTH)).height(Length::Fixed(BAR_TRACK_HEIGHT)))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(Color {
|
background: Some(Background::Color(
|
||||||
r: 0.18,
|
theme.extended_palette().background.strong.color
|
||||||
g: 0.18,
|
)),
|
||||||
b: 0.18,
|
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -107,9 +101,7 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
// ── Card body ────────────────────────────────────────────────────────
|
// ── Card body ────────────────────────────────────────────────────────
|
||||||
let title = text("Opening file")
|
let title = text("Opening file").size(15);
|
||||||
.size(15)
|
|
||||||
.color(Color::WHITE);
|
|
||||||
|
|
||||||
let name_line = text(format!(
|
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)
|
format_size(progress.size_bytes)
|
||||||
))
|
))
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(Color {
|
.style(|theme: &Theme| iced::widget::text::Style {
|
||||||
r: 0.82,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.82)),
|
||||||
g: 0.82,
|
|
||||||
b: 0.82,
|
|
||||||
a: 1.0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let phase_line = text(format!(
|
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
|
basis_points as f32 / 100.0
|
||||||
))
|
))
|
||||||
.size(12)
|
.size(12)
|
||||||
.color(Color {
|
.style(|theme: &Theme| iced::widget::text::Style {
|
||||||
r: 0.70,
|
color: Some(theme.extended_palette().primary.base.color),
|
||||||
g: 0.80,
|
|
||||||
b: 0.95,
|
|
||||||
a: 1.0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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)
|
.on_press(Message::OpenCancel)
|
||||||
.style(|_: &Theme, status| {
|
.style(button::danger)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.padding([4, 14])
|
.padding([4, 14])
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
|
@ -187,35 +137,25 @@ pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Messag
|
||||||
.width(Length::Fixed(CARD_WIDTH)),
|
.width(Length::Fixed(CARD_WIDTH)),
|
||||||
)
|
)
|
||||||
.padding([18, 22])
|
.padding([18, 22])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(Color {
|
let palette = theme.extended_palette();
|
||||||
r: 0.13,
|
container::Style {
|
||||||
g: 0.13,
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
b: 0.13,
|
|
||||||
a: 0.98,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: Color {
|
color: palette.background.neutral.color,
|
||||||
r: 0.45,
|
|
||||||
g: 0.45,
|
|
||||||
b: 0.45,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 6.0.into(),
|
radius: 6.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Backdrop (click-blocker + dim) ────────────────────────────────────
|
// ── Backdrop (click-blocker + dim) ────────────────────────────────────
|
||||||
let backdrop: Element<'_, Message> = container(Space::new().width(Fill).height(Fill))
|
let backdrop: Element<'_, Message> = container(Space::new().width(Fill).height(Fill))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(Color {
|
background: Some(Background::Color(
|
||||||
r: 0.0,
|
theme.extended_palette().background.strong.color.scale_alpha(0.72)
|
||||||
g: 0.0,
|
)),
|
||||||
b: 0.0,
|
|
||||||
a: 0.55,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,123 @@
|
||||||
|
use crate::app::config::UiThemeConfig;
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, pick_list, row, text, Space};
|
use iced::widget::{
|
||||||
use iced::{Background, Border, Color, Element, Fill, Theme};
|
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> {
|
pub fn view_window<'a>(
|
||||||
const BG: Color = Color {
|
default_save_format: &'a str,
|
||||||
r: 0.15,
|
ui_theme: &'a UiThemeConfig,
|
||||||
g: 0.15,
|
theme_color_inputs: &'a [String; 6],
|
||||||
b: 0.17,
|
) -> Element<'a, Message> {
|
||||||
a: 1.0,
|
let selected_format = crate::io::SAVE_FORMAT_OPTIONS
|
||||||
};
|
|
||||||
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
|
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.find(|candidate| *candidate == default_save_format);
|
.find(|candidate| *candidate == default_save_format);
|
||||||
|
|
||||||
let close = button(text("Close").size(12).color(TEXT))
|
let theme_options = Theme::ALL
|
||||||
.on_press(Message::CloseModal)
|
.iter()
|
||||||
.padding([5, 16])
|
.map(ToString::to_string)
|
||||||
.style(|_: &Theme, status| button::Style {
|
.chain(std::iter::once("Custom".to_string()))
|
||||||
background: Some(Background::Color(match status {
|
.collect::<Vec<_>>();
|
||||||
button::Status::Hovered | button::Status::Pressed => Color {
|
let selected_theme = Some(ui_theme.name.clone());
|
||||||
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 body = column![
|
let palette = ui_theme.palette.to_iced();
|
||||||
text("Open and Save").size(15).color(TEXT),
|
let colors = [
|
||||||
Space::new().height(16),
|
("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![
|
row![
|
||||||
text("Default save format:").size(12).color(TEXT).width(150),
|
text("Default save format:").size(12).width(150),
|
||||||
pick_list(crate::io::SAVE_FORMAT_OPTIONS, selected, |format: &str| {
|
pick_list(
|
||||||
Message::DefaultSaveFormatChanged(format.to_string())
|
crate::io::SAVE_FORMAT_OPTIONS,
|
||||||
})
|
selected_format,
|
||||||
|
|format: &str| Message::DefaultSaveFormatChanged(format.to_string())
|
||||||
|
)
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
]
|
]
|
||||||
.spacing(12)
|
.spacing(12)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
Space::new().height(10),
|
Space::new().height(8),
|
||||||
text(
|
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)
|
.size(11),
|
||||||
.color(DIM)
|
Space::new().height(22),
|
||||||
.width(Fill),
|
text("Theme").size(15),
|
||||||
Space::new().height(Fill),
|
Space::new().height(10),
|
||||||
row![Space::new().width(Fill), close],
|
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)
|
.spacing(0)
|
||||||
|
.width(Fill);
|
||||||
|
|
||||||
|
let body = column![
|
||||||
|
scrollable(content).height(Fill),
|
||||||
|
Space::new().height(12),
|
||||||
|
row![Space::new().width(Fill), close],
|
||||||
|
]
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill);
|
.height(Fill);
|
||||||
|
|
||||||
container(body)
|
container(body)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::rounded_box)
|
||||||
background: Some(Background::Color(BG)),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([16, 18])
|
.padding([16, 18])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,7 @@ use iced::widget::{
|
||||||
button, checkbox, column, container, mouse_area, pick_list, row, scrollable, text, text_input,
|
button, checkbox, column, container, mouse_area, pick_list, row, scrollable, text, text_input,
|
||||||
Space,
|
Space,
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Length, Theme};
|
use iced::{Background, Border, 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 };
|
|
||||||
|
|
||||||
/// Sentinel entries in the printer dropdown (not real printer names).
|
/// Sentinel entries in the printer dropdown (not real printer names).
|
||||||
pub const OUT_DEFAULT: &str = "System default printer";
|
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 {
|
fn btn(accent: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
|
||||||
move |_: &Theme, st| button::Style {
|
move |theme: &Theme, st| {
|
||||||
background: Some(Background::Color(match (accent, st) {
|
let palette = theme.extended_palette();
|
||||||
(true, button::Status::Hovered | button::Status::Pressed) => {
|
let pair = match (accent, st) {
|
||||||
Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 }
|
(true, button::Status::Hovered | button::Status::Pressed) => palette.primary.strong,
|
||||||
}
|
|
||||||
(false, button::Status::Hovered | button::Status::Pressed) => {
|
(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,
|
(true, _) => palette.primary.base,
|
||||||
_ => Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 },
|
_ => palette.background.weak,
|
||||||
})),
|
};
|
||||||
text_color: TEXT,
|
button::Style {
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
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(),
|
shadow: iced::Shadow::default(),
|
||||||
snap: false,
|
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 {
|
text_input::Style {
|
||||||
background: Background::Color(FIELD),
|
background: Background::Color(palette.background.base.color),
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
border: Border { color: border, width: 1.0, radius: 3.0.into() },
|
||||||
icon: TEXT,
|
icon: palette.background.base.text,
|
||||||
placeholder: DIM,
|
placeholder: palette.background.base.text.scale_alpha(0.48),
|
||||||
value: TEXT,
|
value: palette.background.base.text,
|
||||||
selection: ACCENT,
|
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))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section_label<'a>(s: &'static str) -> Element<'a, Message> {
|
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> {
|
fn vsep<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(1).height(Fill))
|
container(Space::new().width(1).height(Fill))
|
||||||
.width(1)
|
.width(1)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -295,13 +306,16 @@ fn setup_row<'a>(
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
let is_sel = name == selected;
|
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])
|
.padding([4, 8])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: is_sel.then_some(Background::Color(ACTIVE)),
|
let palette = theme.extended_palette();
|
||||||
text_color: Some(TEXT),
|
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()
|
..Default::default()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mouse_area(cell)
|
mouse_area(cell)
|
||||||
.on_press(Message::PlotDlg(PlotDlgMsg::SelectSetup(name.to_string())))
|
.on_press(Message::PlotDlg(PlotDlgMsg::SelectSetup(name.to_string())))
|
||||||
|
|
@ -321,7 +335,7 @@ fn drop_row<'a>(
|
||||||
.text_size(12)
|
.text_size(12)
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.width(Length::Fill);
|
.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)
|
.spacing(8)
|
||||||
.align_y(iced::Center)
|
.align_y(iced::Center)
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -335,7 +349,7 @@ fn field_row<'a>(
|
||||||
width: u16,
|
width: u16,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
row![
|
row![
|
||||||
text(label).size(11).color(DIM).width(92),
|
text(label).size(11).style(muted_style).width(92),
|
||||||
text_input("", value)
|
text_input("", value)
|
||||||
.on_input(move |s| Message::PlotDlg(ctor(s)))
|
.on_input(move |s| Message::PlotDlg(ctor(s)))
|
||||||
.style(field_style)
|
.style(field_style)
|
||||||
|
|
@ -413,8 +427,10 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||||
]
|
]
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weak.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.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))
|
.map(|name| setup_row(name, &s.selected_setup, renaming, rename_buf))
|
||||||
.collect();
|
.collect();
|
||||||
let list_body: Element<'_, Message> = if rows.is_empty() {
|
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])
|
.padding([6, 8])
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -453,16 +469,19 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||||
};
|
};
|
||||||
let list_panel = container(
|
let list_panel = container(
|
||||||
column![
|
column![
|
||||||
text("Page setups").size(10).color(DIM),
|
text("Page setups").size(10).style(muted_style),
|
||||||
container(list_body)
|
container(list_body)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(LIST)),
|
let palette = theme.extended_palette();
|
||||||
|
container::Style {
|
||||||
|
background: Some(Background::Color(palette.background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
color: BORDER,
|
color: palette.background.neutral.color,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 3.0.into(),
|
radius: 3.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
|
@ -550,11 +569,18 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||||
hdivider(),
|
hdivider(),
|
||||||
section_label("Plot style table (pen assignments)"),
|
section_label("Plot style table (pen assignments)"),
|
||||||
row![
|
row![
|
||||||
container(text(style_label).size(12).color(TEXT))
|
container(text(style_label).size(12))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(FIELD)),
|
let palette = theme.extended_palette();
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
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()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.padding([4, 8])
|
.padding([4, 8])
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
|
|
@ -583,7 +609,7 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
)
|
)
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
text("DPI").size(11).color(DIM),
|
text("DPI").size(11).style(muted_style),
|
||||||
text_input("", &s.dpi)
|
text_input("", &s.dpi)
|
||||||
.on_input(move |v| Message::PlotDlg(PlotDlgMsg::Dpi(v)))
|
.on_input(move |v| Message::PlotDlg(PlotDlgMsg::Dpi(v)))
|
||||||
.style(field_style)
|
.style(field_style)
|
||||||
|
|
@ -631,8 +657,10 @@ pub fn view_window(s: &PlotDialogState) -> Element<'_, Message> {
|
||||||
let body = row![list_panel, vsep(), detail].height(Fill);
|
let body = row![list_panel, vsep(), detail].height(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), body].spacing(0))
|
container(column![toolbar, hdivider(), body].spacing(0))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
use crate::plugin::external::{ExternalPlugin, RegistryEntry};
|
||||||
use iced::widget::{button, column, container, pick_list, row, scrollable, text, text_input, Space};
|
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};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
/// Marketplace state passed to the Plugin Manager view.
|
/// Marketplace state passed to the Plugin Manager view.
|
||||||
|
|
@ -25,124 +25,105 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||||
names: &["PLUGINS", "PLUGINMANAGER"]
|
names: &["PLUGINS", "PLUGINMANAGER"]
|
||||||
});
|
});
|
||||||
|
|
||||||
const BG: Color = Color {
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.15,
|
iced::widget::text::Style {
|
||||||
g: 0.15,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
b: 0.15,
|
}
|
||||||
a: 1.0,
|
}
|
||||||
};
|
|
||||||
const CARD: Color = Color {
|
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.12,
|
iced::widget::text::Style {
|
||||||
g: 0.12,
|
color: Some(theme.extended_palette().primary.base.color),
|
||||||
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 badge<'a>(label: String) -> Element<'a, Message> {
|
fn badge<'a>(label: String) -> Element<'a, Message> {
|
||||||
container(text(label).size(11).color(WHITE))
|
container(text(label).size(11))
|
||||||
.padding([2, 8])
|
.padding([2, 8])
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| {
|
||||||
background: Some(Background::Color(Color {
|
let pair = theme.extended_palette().primary.weak;
|
||||||
r: 0.20,
|
container::Style {
|
||||||
g: 0.34,
|
background: Some(Background::Color(pair.color)),
|
||||||
b: 0.52,
|
text_color: Some(pair.text),
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_button<'a>(id: &str, disabled: bool) -> Element<'a, Message> {
|
fn toggle_button<'a>(id: &str, disabled: bool) -> Element<'a, Message> {
|
||||||
// Label shows the action the click performs.
|
// Label shows the action the click performs.
|
||||||
let (label, on, off) = if disabled {
|
let label = 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 })
|
"Enable"
|
||||||
} else {
|
} 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 want_enabled = disabled; // clicking flips the state
|
||||||
let id_owned = id.to_string();
|
let id_owned = id.to_string();
|
||||||
button(text(label).size(12).color(WHITE))
|
button(text(label).size(12))
|
||||||
.padding([3, 12])
|
.padding([3, 12])
|
||||||
.on_press(Message::SetPluginEnabled(id_owned, want_enabled))
|
.on_press(Message::SetPluginEnabled(id_owned, want_enabled))
|
||||||
.style(move |_: &Theme, status| {
|
.style(if disabled { button::success } else { button::danger })
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum StatusKind {
|
||||||
|
Muted,
|
||||||
|
Success,
|
||||||
|
Danger,
|
||||||
|
Warning,
|
||||||
|
}
|
||||||
|
|
||||||
/// Coloured status pill for a discovered external package.
|
/// Coloured status pill for a discovered external package.
|
||||||
fn status_badge<'a>(label: &str, color: Color) -> Element<'a, Message> {
|
fn status_badge<'a>(label: &str, kind: StatusKind) -> Element<'a, Message> {
|
||||||
container(text(label.to_string()).size(11).color(WHITE))
|
container(text(label.to_string()).size(11))
|
||||||
.padding([2, 8])
|
.padding([2, 8])
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(color)),
|
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 {
|
border: Border {
|
||||||
radius: 4.0.into(),
|
radius: 4.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Element<'a, Message> {
|
fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Element<'a, Message> {
|
||||||
let (status, color) = if loaded && disabled {
|
let (status, kind) = if loaded && disabled {
|
||||||
("Disabled", Color { r: 0.45, g: 0.45, b: 0.45, a: 1.0 })
|
("Disabled", StatusKind::Muted)
|
||||||
} else if loaded {
|
} 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() {
|
} 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 {
|
} 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 {
|
} 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![
|
let mut header = row![
|
||||||
text(p.name.clone()).size(15).color(WHITE),
|
text(p.name.clone()).size(15),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
badge(format!("v{}", p.version)),
|
badge(format!("v{}", p.version)),
|
||||||
Space::new().width(8),
|
Space::new().width(8),
|
||||||
badge(format!("API {}", p.api_version)),
|
badge(format!("API {}", p.api_version)),
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
status_badge(status, color),
|
status_badge(status, kind),
|
||||||
]
|
]
|
||||||
.align_y(iced::Center);
|
.align_y(iced::Center);
|
||||||
// A loaded plugin can be turned off (drops its ribbon tab + dispatch).
|
// 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(
|
header = header.push(pill_button(
|
||||||
"Uninstall",
|
"Uninstall",
|
||||||
Message::PluginUninstall(p.id.clone()),
|
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);
|
let mut body = column![header, id_line].spacing(5);
|
||||||
if !p.description.is_empty() {
|
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() {
|
if !p.command_prefixes.is_empty() {
|
||||||
body = body.push(
|
body = body.push(
|
||||||
text(format!("Commands: {}", p.command_prefixes.join(", ")))
|
text(format!("Commands: {}", p.command_prefixes.join(", ")))
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(DIM),
|
.style(muted_style),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
container(body.padding([12, 14]))
|
container(body.padding([12, 14]))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(CARD)),
|
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pill_button<'a>(label: &str, msg: Message, bg: Color) -> Element<'a, Message> {
|
fn pill_button<'a>(
|
||||||
button(text(label.to_string()).size(12).color(WHITE))
|
label: &str,
|
||||||
|
msg: Message,
|
||||||
|
style: fn(&Theme, button::Status) -> button::Style,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
button(text(label.to_string()).size(12))
|
||||||
.padding([4, 12])
|
.padding([4, 12])
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(move |_: &Theme, status| {
|
.style(style)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Square icon variant of [`pill_button`] for glyph-free actions (e.g. remove).
|
/// 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> {
|
fn pill_icon_button<'a>(
|
||||||
button(crate::ui::icons::tinted(icon, 11.0, WHITE))
|
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])
|
.padding([5, 9])
|
||||||
.on_press(msg)
|
.on_press(msg)
|
||||||
.style(move |_: &Theme, status| {
|
.style(style)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into()
|
.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.
|
/// Release dropdown + Install (+ optional unlink) for one repo.
|
||||||
fn install_controls<'a>(
|
fn install_controls<'a>(
|
||||||
repo: &str,
|
repo: &str,
|
||||||
|
|
@ -232,7 +190,7 @@ fn install_controls<'a>(
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let repo_s = repo.to_string();
|
let repo_s = repo.to_string();
|
||||||
let picker: Element<'_, Message> = if tags.is_empty() {
|
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 {
|
} else {
|
||||||
let r = repo_s.clone();
|
let r = repo_s.clone();
|
||||||
pick_list(tags, selected, move |tag| {
|
pick_list(tags, selected, move |tag| {
|
||||||
|
|
@ -244,7 +202,11 @@ fn install_controls<'a>(
|
||||||
let mut controls = row![
|
let mut controls = row![
|
||||||
picker,
|
picker,
|
||||||
Space::new().width(8),
|
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)
|
.align_y(iced::Center)
|
||||||
.spacing(4);
|
.spacing(4);
|
||||||
|
|
@ -253,7 +215,7 @@ fn install_controls<'a>(
|
||||||
controls = controls.push(pill_icon_button(
|
controls = controls.push(pill_icon_button(
|
||||||
crate::ui::icons::CLOSE,
|
crate::ui::icons::CLOSE,
|
||||||
Message::PluginRepoRemove(repo_s),
|
Message::PluginRepoRemove(repo_s),
|
||||||
RED,
|
button::danger,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
controls.into()
|
controls.into()
|
||||||
|
|
@ -262,37 +224,33 @@ fn install_controls<'a>(
|
||||||
fn market_card<'a>(body: iced::widget::Column<'a, Message>) -> Element<'a, Message> {
|
fn market_card<'a>(body: iced::widget::Column<'a, Message>) -> Element<'a, Message> {
|
||||||
container(body.spacing(4).padding([10, 12]))
|
container(body.spacing(4).padding([10, 12]))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box)
|
||||||
background: Some(Background::Color(CARD)),
|
|
||||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
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).
|
// Curated registry entries (from the OpenCADStudio repo).
|
||||||
for e in m.registry {
|
for e in m.registry {
|
||||||
let tags = m.release_tags.get(&e.repo).cloned().unwrap_or_default();
|
let tags = m.release_tags.get(&e.repo).cloned().unwrap_or_default();
|
||||||
let selected = m.selected_tag.get(&e.repo).cloned();
|
let selected = m.selected_tag.get(&e.repo).cloned();
|
||||||
let header = row![
|
let header = row![
|
||||||
text(e.name.clone()).size(14).color(WHITE),
|
text(e.name.clone()).size(14),
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
install_controls(&e.repo, tags, selected, false),
|
install_controls(&e.repo, tags, selected, false),
|
||||||
]
|
]
|
||||||
.align_y(iced::Center);
|
.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() {
|
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));
|
col = col.push(market_card(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manual: link any repo by owner/repo.
|
// Manual: link any repo by owner/repo.
|
||||||
col = col.push(Space::new().height(6));
|
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(
|
col = col.push(
|
||||||
row![
|
row![
|
||||||
text_input("owner/repo", m.input)
|
text_input("owner/repo", m.input)
|
||||||
|
|
@ -301,7 +259,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||||
.size(13)
|
.size(13)
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
Space::new().width(8),
|
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),
|
.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 tags = m.release_tags.get(repo).cloned().unwrap_or_default();
|
||||||
let selected = m.selected_tag.get(repo).cloned();
|
let selected = m.selected_tag.get(repo).cloned();
|
||||||
let header = row![
|
let header = row![
|
||||||
text(repo.clone()).size(13).color(WHITE),
|
text(repo.clone()).size(13),
|
||||||
Space::new().width(Fill),
|
Space::new().width(Fill),
|
||||||
install_controls(repo, tags, selected, true),
|
install_controls(repo, tags, selected, true),
|
||||||
]
|
]
|
||||||
|
|
@ -318,7 +276,7 @@ fn marketplace_section<'a>(m: &MarketView) -> Element<'a, Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.status.is_empty() {
|
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()
|
col.into()
|
||||||
}
|
}
|
||||||
|
|
@ -329,17 +287,17 @@ pub fn view_window<'a>(
|
||||||
loaded: &FxHashSet<String>,
|
loaded: &FxHashSet<String>,
|
||||||
market: MarketView,
|
market: MarketView,
|
||||||
) -> Element<'a, Message> {
|
) -> 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.")
|
let subtitle = text("Add-ons load from the plugins folder. Install from a repository below.")
|
||||||
.size(12)
|
.size(12)
|
||||||
.color(DIM);
|
.style(muted_style);
|
||||||
|
|
||||||
let mut list = column![].spacing(10);
|
let mut list = column![].spacing(10);
|
||||||
// Installed external packages (from the plugins folder).
|
// Installed external packages (from the plugins folder).
|
||||||
if externals.is_empty() {
|
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 {
|
} else {
|
||||||
list = list.push(text("Installed").size(13).color(ACCENT));
|
list = list.push(text("Installed").size(13).style(primary_style));
|
||||||
for p in externals {
|
for p in externals {
|
||||||
list = list.push(external_card(
|
list = list.push(external_card(
|
||||||
p,
|
p,
|
||||||
|
|
@ -360,8 +318,10 @@ pub fn view_window<'a>(
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill),
|
.height(Fill),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{column, container, row, scrollable, text, Space};
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Display name of the primary accelerator modifier on this platform.
|
/// Display name of the primary accelerator modifier on this platform.
|
||||||
|
|
@ -14,49 +14,26 @@ const MOD: &str = "Cmd";
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
const MOD: &str = "Ctrl";
|
const MOD: &str = "Ctrl";
|
||||||
|
|
||||||
const TB: Color = Color {
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.13,
|
iced::widget::text::Style {
|
||||||
g: 0.13,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
b: 0.13,
|
}
|
||||||
a: 1.0,
|
}
|
||||||
};
|
|
||||||
const BG: Color = Color {
|
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.15,
|
iced::widget::text::Style {
|
||||||
g: 0.15,
|
color: Some(theme.extended_palette().primary.base.color),
|
||||||
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 hdivider<'a>() -> Element<'a, Message> {
|
fn hdivider<'a>() -> Element<'a, Message> {
|
||||||
container(Space::new().width(Fill).height(1))
|
container(Space::new().width(Fill).height(1))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(1)
|
.height(1)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BORDER)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.neutral.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -69,7 +46,7 @@ fn shortcut_row<'a>(
|
||||||
row![
|
row![
|
||||||
text(key.into())
|
text(key.into())
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(KEY)
|
.style(primary_style)
|
||||||
.font(iced::Font::MONOSPACE)
|
.font(iced::Font::MONOSPACE)
|
||||||
.width(160),
|
.width(160),
|
||||||
text(action).size(11),
|
text(action).size(11),
|
||||||
|
|
@ -81,7 +58,7 @@ fn shortcut_row<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section<'a>(title: impl Into<Cow<'static, str>>) -> Element<'a, Message> {
|
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 {
|
.padding(iced::Padding {
|
||||||
top: 6.0,
|
top: 6.0,
|
||||||
right: 0.0,
|
right: 0.0,
|
||||||
|
|
@ -99,12 +76,14 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text("Type SHORTCUTS SET <key> <cmd> to add custom shortcuts.")
|
text("Type SHORTCUTS SET <key> <cmd> to add custom shortcuts.")
|
||||||
.size(10)
|
.size(10)
|
||||||
.color(DIM),
|
.style(muted_style),
|
||||||
]
|
]
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(TB)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.weakest.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
@ -147,7 +126,7 @@ pub fn view_window<'a>(
|
||||||
rows.push(
|
rows.push(
|
||||||
text(" (none — use: SHORTCUTS SET <key> <command>)")
|
text(" (none — use: SHORTCUTS SET <key> <command>)")
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(DIM)
|
.style(muted_style)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -158,7 +137,7 @@ pub fn view_window<'a>(
|
||||||
row![
|
row![
|
||||||
text(key.as_str())
|
text(key.as_str())
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(KEY)
|
.style(primary_style)
|
||||||
.font(iced::Font::MONOSPACE)
|
.font(iced::Font::MONOSPACE)
|
||||||
.width(160),
|
.width(160),
|
||||||
text(cmd.as_str()).size(11),
|
text(cmd.as_str()).size(11),
|
||||||
|
|
@ -179,26 +158,25 @@ pub fn view_window<'a>(
|
||||||
// ── Header row with accent ────────────────────────────────────────────
|
// ── Header row with accent ────────────────────────────────────────────
|
||||||
let header = container(
|
let header = container(
|
||||||
row![
|
row![
|
||||||
text("Key").size(10).color(ACCENT).width(160),
|
text("Key").size(10).style(primary_style).width(160),
|
||||||
text("Action").size(10).color(ACCENT),
|
text("Action").size(10).style(primary_style),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.padding([4, 16]),
|
.padding([4, 16]),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(Color {
|
background: Some(Background::Color(
|
||||||
r: 0.13,
|
theme.extended_palette().primary.weak.color,
|
||||||
g: 0.13,
|
)),
|
||||||
b: 0.18,
|
|
||||||
a: 1.0,
|
|
||||||
})),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
||||||
container(column![toolbar, hdivider(), header, hdivider(), content].spacing(0))
|
container(column![toolbar, hdivider(), header, hdivider(), content].spacing(0))
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,33 @@
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
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 {
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.15,
|
iced::widget::text::Style {
|
||||||
g: 0.15,
|
color: Some(theme.extended_palette().background.base.text.scale_alpha(0.68)),
|
||||||
b: 0.15,
|
}
|
||||||
a: 1.0,
|
}
|
||||||
};
|
|
||||||
const PANEL: Color = Color {
|
fn primary_style(theme: &Theme) -> iced::widget::text::Style {
|
||||||
r: 0.12,
|
iced::widget::text::Style {
|
||||||
g: 0.12,
|
color: Some(theme.extended_palette().primary.base.color),
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Renders one of the two "Installed" / "Latest" cards. The `highlight`
|
/// Renders one of the two "Installed" / "Latest" cards. The `highlight`
|
||||||
/// flag tints the border + label with the accent colour, making the new
|
/// flag tints the border + label with the accent colour, making the new
|
||||||
/// version the visual anchor of the row.
|
/// version the visual anchor of the row.
|
||||||
fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Element<'a, Message> {
|
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(
|
container(
|
||||||
column![
|
column![
|
||||||
text(label).size(10).color(label_color),
|
text(label)
|
||||||
text(value).size(20).color(value_color),
|
.size(10)
|
||||||
|
.style(move |theme: &Theme| if highlight {
|
||||||
|
primary_style(theme)
|
||||||
|
} else {
|
||||||
|
muted_style(theme)
|
||||||
|
}),
|
||||||
|
text(value).size(20),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_x(iced::Center),
|
.align_x(iced::Center),
|
||||||
|
|
@ -67,14 +40,26 @@ fn version_card<'a>(label: &'static str, value: String, highlight: bool) -> Elem
|
||||||
left: 12.0,
|
left: 12.0,
|
||||||
})
|
})
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |theme: &Theme| {
|
||||||
background: Some(Background::Color(bg)),
|
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 {
|
border: Border {
|
||||||
color: border_color,
|
color: if highlight {
|
||||||
|
palette.primary.base.color
|
||||||
|
} else {
|
||||||
|
palette.background.neutral.color
|
||||||
|
},
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 6.0.into(),
|
radius: 6.0.into(),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -98,24 +83,23 @@ fn render_notes_line<'a>(raw: &str) -> Element<'a, Message> {
|
||||||
if let Some(rest) = trimmed.strip_prefix("## ") {
|
if let Some(rest) = trimmed.strip_prefix("## ") {
|
||||||
return text(strip_inline_md(rest))
|
return text(strip_inline_md(rest))
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(ACCENT)
|
.style(primary_style)
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
if let Some(rest) = trimmed.strip_prefix("### ") {
|
if let Some(rest) = trimmed.strip_prefix("### ") {
|
||||||
return text(strip_inline_md(rest))
|
return text(strip_inline_md(rest))
|
||||||
.size(12)
|
.size(12)
|
||||||
.color(WHITE)
|
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
if let Some(rest) = trimmed.strip_prefix("- ").or_else(|| trimmed.strip_prefix("* ")) {
|
if let Some(rest) = trimmed.strip_prefix("- ").or_else(|| trimmed.strip_prefix("* ")) {
|
||||||
return row![
|
return row![
|
||||||
container(crate::ui::icons::tinted(crate::ui::icons::DOT, 5.0, DIM)).width(14),
|
container(crate::ui::icons::themed_secondary(crate::ui::icons::DOT, 5.0)).width(14),
|
||||||
text(strip_inline_md(rest)).size(11).color(WHITE),
|
text(strip_inline_md(rest)).size(11),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.into();
|
.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
|
/// 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> {
|
pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||||
let header = container(
|
let header = container(
|
||||||
column![
|
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.")
|
text("A newer Open CAD Studio version is published on GitHub.")
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(DIM),
|
.style(muted_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_x(iced::Center),
|
.align_x(iced::Center),
|
||||||
|
|
@ -165,10 +149,9 @@ pub fn view_window<'a>(latest: &'a str, body: &'a str) -> Element<'a, Message> {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
let latest_card = version_card("Latest", format!("v{}", latest), true);
|
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,
|
crate::ui::icons::ARROW_LONG_RIGHT,
|
||||||
20.0,
|
20.0,
|
||||||
DIM,
|
|
||||||
))
|
))
|
||||||
.width(iced::Length::Fixed(32.0))
|
.width(iced::Length::Fixed(32.0))
|
||||||
.height(Fill)
|
.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))
|
let later_btn = button(text("Later").size(11))
|
||||||
.on_press(Message::UpdateNoticeClose)
|
.on_press(Message::UpdateNoticeClose)
|
||||||
.style(|_: &Theme, st| button::Style {
|
.style(button::secondary)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([6, 16]);
|
.padding([6, 16]);
|
||||||
|
|
||||||
let open_btn = button(text("Open Release Page").size(11))
|
let open_btn = button(text("Open Release Page").size(11))
|
||||||
.on_press(Message::UpdateNoticeOpenRelease)
|
.on_press(Message::UpdateNoticeOpenRelease)
|
||||||
.style(|_: &Theme, st| button::Style {
|
.style(button::primary)
|
||||||
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()
|
|
||||||
})
|
|
||||||
.padding([6, 16]);
|
.padding([6, 16]);
|
||||||
|
|
||||||
let footer = row![Space::new().width(Fill), later_btn, open_btn]
|
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
|
// Release notes panel. Rendered as a light-markdown column inside a
|
||||||
// bordered scrollable so long bodies stay contained and don't
|
// bordered scrollable so long bodies stay contained and don't
|
||||||
// explode the window. Empty body → "No release notes provided."
|
// 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 {
|
.padding(iced::Padding {
|
||||||
top: 10.0,
|
top: 10.0,
|
||||||
right: 0.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() {
|
let notes_body: Element<'a, Message> = if body.trim().is_empty() {
|
||||||
text("No release notes provided.")
|
text("No release notes provided.")
|
||||||
.size(11)
|
.size(11)
|
||||||
.color(DIM)
|
.style(muted_style)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
let mut col = column![].spacing(4);
|
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)
|
let notes_block = container(notes_body)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(container::bordered_box);
|
||||||
background: Some(Background::Color(PANEL)),
|
|
||||||
border: Border {
|
|
||||||
color: BORDER,
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wrap notes_block in a Fill-height container outside the column so it
|
// Wrap notes_block in a Fill-height container outside the column so it
|
||||||
// greedily claims every pixel left over after the fixed-height rows
|
// 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,
|
left: 20.0,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|theme: &Theme| container::Style {
|
||||||
background: Some(Background::Color(BG)),
|
background: Some(Background::Color(
|
||||||
|
theme.extended_palette().background.base.color,
|
||||||
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use iced::advanced::layout::{self, Layout};
|
||||||
use iced::advanced::widget::{self, tree, Widget};
|
use iced::advanced::widget::{self, tree, Widget};
|
||||||
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
use iced::advanced::{mouse, overlay, renderer, Clipboard, Renderer as _, Shell};
|
||||||
use iced::{
|
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,
|
Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1360,12 +1360,7 @@ impl<'a> Widget<Message, Theme, Renderer> for ReorderTab<'a> {
|
||||||
shadow: Shadow::default(),
|
shadow: Shadow::default(),
|
||||||
snap: true,
|
snap: true,
|
||||||
},
|
},
|
||||||
Background::Color(Color {
|
Background::Color(theme.extended_palette().primary.base.color),
|
||||||
r: 0.20,
|
|
||||||
g: 0.55,
|
|
||||||
b: 0.90,
|
|
||||||
a: 1.0,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue