feat(ui): refine Graphite visuals and center navigation content
This commit is contained in:
1 parent
cc3b6f903f
commit
1e34ec47ad
14 files changed
+646
-121
No files matched your search
@@ -567,5 +567,7 @@
|
||||
"Application icons": "应用图标",
|
||||
"Refresh applications": "刷新应用列表",
|
||||
"Panel status icons": "面板状态图标",
|
||||
"Install the matching Ferese compositor beside Settings.": "请将匹配版本的 Ferese 主程序与设置程序安装在同一目录。"
|
||||
"Install the matching Ferese compositor beside Settings.": "请将匹配版本的 Ferese 主程序与设置程序安装在同一目录。",
|
||||
"Quick access": "快速访问",
|
||||
"Saving…": "正在保存…"
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::theme::Appearance;
|
||||
pub use ferese_theme_model::families::{Family, Palette};
|
||||
|
||||
pub const BUILTINS: [(&str, &str, &str, &str); 6] = [
|
||||
pub const BUILTINS: [(&str, &str, &str, &str); 7] = [
|
||||
(
|
||||
"ferese-blue",
|
||||
"Ferese Blue",
|
||||
"ferese-blue-light",
|
||||
"ferese-blue",
|
||||
),
|
||||
("graphite", "Graphite", "graphite-light", "graphite"),
|
||||
(
|
||||
"catppuccin",
|
||||
"Catppuccin",
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct Preset {
|
||||
pub shadow_opacity: f64,
|
||||
}
|
||||
|
||||
pub const PRESETS: [Preset; 12] = [
|
||||
pub const PRESETS: [Preset; 14] = [
|
||||
Preset {
|
||||
appearance: Appearance::Dark,
|
||||
id: "ferese-blue",
|
||||
@@ -38,6 +38,24 @@ pub const PRESETS: [Preset; 12] = [
|
||||
tint_strength: 0.5,
|
||||
shadow_opacity: 0.2,
|
||||
},
|
||||
Preset {
|
||||
appearance: Appearance::Dark,
|
||||
id: "graphite",
|
||||
name: "Graphite",
|
||||
description: "Quiet graphite",
|
||||
base: "#101113",
|
||||
raised: "#1B1D21",
|
||||
application_background: "#141518",
|
||||
text: "#E7E9ED",
|
||||
muted: "#9A9FA9",
|
||||
accent: "#8EABD8",
|
||||
border: "#FFFFFF16",
|
||||
gradient_end: "#8EABD8",
|
||||
opacity: 0.96,
|
||||
blur_radius: 8.0,
|
||||
tint_strength: 0.06,
|
||||
shadow_opacity: 0.12,
|
||||
},
|
||||
Preset {
|
||||
appearance: Appearance::Dark,
|
||||
id: "catppuccin-mocha",
|
||||
@@ -146,6 +164,24 @@ pub const PRESETS: [Preset; 12] = [
|
||||
tint_strength: 1.0,
|
||||
shadow_opacity: 0.1,
|
||||
},
|
||||
Preset {
|
||||
appearance: Appearance::Light,
|
||||
id: "graphite-light",
|
||||
name: "Graphite",
|
||||
description: "Quiet paper",
|
||||
base: "#F7F8FA",
|
||||
raised: "#FFFFFF",
|
||||
application_background: "#F1F3F5",
|
||||
text: "#23262C",
|
||||
muted: "#626A76",
|
||||
accent: "#3F6DAA",
|
||||
border: "#00000018",
|
||||
gradient_end: "#3F6DAA",
|
||||
opacity: 0.98,
|
||||
blur_radius: 4.0,
|
||||
tint_strength: 0.02,
|
||||
shadow_opacity: 0.08,
|
||||
},
|
||||
Preset {
|
||||
appearance: Appearance::Light,
|
||||
id: "catppuccin-latte",
|
||||
|
||||
@@ -1358,8 +1358,8 @@ mod tests {
|
||||
.iter()
|
||||
.filter(|p| p.appearance == Appearance::Light)
|
||||
.collect();
|
||||
assert_eq!(dark.len(), 6);
|
||||
assert_eq!(light.len(), 6);
|
||||
assert_eq!(dark.len(), crate::families::BUILTINS.len());
|
||||
assert_eq!(light.len(), crate::families::BUILTINS.len());
|
||||
for (dark, light) in dark.iter().zip(light) {
|
||||
assert_eq!(dark.name, light.name);
|
||||
assert_ne!(dark.base, light.base);
|
||||
@@ -1443,7 +1443,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(resolved.policy.light.family.as_deref(), Some("gruvbox"));
|
||||
assert_eq!(resolved.policy.dark.family.as_deref(), Some("catppuccin"));
|
||||
assert_eq!(resolved.families.len(), 6);
|
||||
assert_eq!(resolved.families.len(), crate::families::BUILTINS.len());
|
||||
}
|
||||
}
|
||||
let resolved = candidate(r#"theme { mode "light"; family "catppuccin"; split #true; light { family "gruvbox"; }; dark { family "everforest"; }; }"#).unwrap();
|
||||
|
||||
@@ -107,6 +107,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
let mut sequences: Vec<Vec<u32>> = Vec::new();
|
||||
let mut clicks = Vec::new();
|
||||
let mut hovers = Vec::new();
|
||||
let mut drag = None;
|
||||
let mut extent = (1280, 800);
|
||||
let mut args = std::env::args().skip(1);
|
||||
@@ -119,6 +120,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
extent = (v[0], v[1]);
|
||||
}
|
||||
"--hover" => {
|
||||
let v = parse_numbers(&args.next().ok_or("missing hover position")?)?;
|
||||
if v.len() != 2 {
|
||||
return Err("invalid hover".into());
|
||||
}
|
||||
hovers.push((v[0], v[1]));
|
||||
}
|
||||
"--click" => {
|
||||
let v = parse_numbers(&args.next().ok_or("missing click position")?)?;
|
||||
if v.len() != 2 {
|
||||
@@ -247,12 +255,24 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
std::thread::sleep(Duration::from_millis(35));
|
||||
}
|
||||
}
|
||||
if !clicks.is_empty() || drag.is_some() {
|
||||
if !clicks.is_empty() || !hovers.is_empty() || drag.is_some() {
|
||||
let pointer = state
|
||||
.pointer_manager
|
||||
.as_ref()
|
||||
.ok_or("test pointer is not enabled on this compositor")?
|
||||
.create_virtual_pointer(state.seat.as_ref(), &qh, ());
|
||||
for (x, y) in hovers {
|
||||
pointer.motion_absolute(
|
||||
started.elapsed().as_millis() as u32,
|
||||
x,
|
||||
y,
|
||||
extent.0,
|
||||
extent.1,
|
||||
);
|
||||
pointer.frame();
|
||||
queue.roundtrip(&mut state)?;
|
||||
std::thread::sleep(Duration::from_millis(120));
|
||||
}
|
||||
for (x, y) in clicks {
|
||||
pointer.motion_absolute(
|
||||
started.elapsed().as_millis() as u32,
|
||||
|
||||
@@ -116,14 +116,24 @@ pub fn settings_input(p: Palette) -> theme::TextInput {
|
||||
let appearance = move |focused: bool, hovered: bool| cosmic::widget::text_input::Appearance {
|
||||
shape: Some(BorderShape::Continuous),
|
||||
outline: None,
|
||||
background: mix(p.card, p.text, if hovered { 0.06 } else { 0.035 }).into(),
|
||||
background: mix(p.background, p.card, if hovered { 0.55 } else { 0.35 }).into(),
|
||||
border_radius: p.radius.min(7.).into(),
|
||||
border_width: 1.,
|
||||
border_offset: None,
|
||||
border_color: if focused {
|
||||
p.accent
|
||||
} else {
|
||||
mix(p.card, p.text, if hovered { 0.26 } else { 0.18 })
|
||||
mix(
|
||||
p.card,
|
||||
p.text,
|
||||
if p.high_contrast {
|
||||
0.5
|
||||
} else if hovered {
|
||||
0.20
|
||||
} else {
|
||||
0.10
|
||||
},
|
||||
)
|
||||
},
|
||||
icon_color: Some(p.muted),
|
||||
text_color: Some(p.text),
|
||||
@@ -166,25 +176,52 @@ fn styled_button(
|
||||
opacity: f32,
|
||||
focus_visible: bool,
|
||||
) -> theme::Button {
|
||||
let style = move |hover: bool, focused: bool| {
|
||||
let background = if selected {
|
||||
mix(p.sidebar, p.accent, if hover { 0.24 } else { 0.17 })
|
||||
} else if hover {
|
||||
if navigation {
|
||||
mix(p.card, crate::surface_shade(p.sidebar), 0.06)
|
||||
} else {
|
||||
mix(p.card, p.text, 0.08)
|
||||
}
|
||||
} else if navigation {
|
||||
let style = move |hover: bool, focused: bool, pressed: bool| {
|
||||
let resting = if navigation {
|
||||
p.sidebar
|
||||
} else {
|
||||
mix(p.card, p.text, 0.035)
|
||||
mix(p.background, p.card, 0.5)
|
||||
};
|
||||
let (background, on) = if selected {
|
||||
crate::accent_pair(background, p.text)
|
||||
let selected_strength = if navigation { 0.09 } else { 0.10 };
|
||||
let background = if selected && navigation {
|
||||
mix(
|
||||
resting,
|
||||
p.text,
|
||||
if pressed {
|
||||
0.16
|
||||
} else if hover {
|
||||
0.13
|
||||
} else {
|
||||
selected_strength
|
||||
},
|
||||
)
|
||||
} else if selected {
|
||||
mix(
|
||||
resting,
|
||||
p.accent,
|
||||
if pressed {
|
||||
0.20
|
||||
} else if hover {
|
||||
0.16
|
||||
} else {
|
||||
selected_strength
|
||||
},
|
||||
)
|
||||
} else if hover {
|
||||
mix(resting, p.text, if pressed { 0.09 } else { 0.055 })
|
||||
} else {
|
||||
(background, p.text)
|
||||
resting
|
||||
};
|
||||
let mut on = crate::foreground(background, p.text);
|
||||
if p.high_contrast && crate::contrast(background, on) < 7. {
|
||||
on = if crate::contrast(background, Color::BLACK)
|
||||
>= crate::contrast(background, Color::WHITE)
|
||||
{
|
||||
Color::BLACK
|
||||
} else {
|
||||
Color::WHITE
|
||||
};
|
||||
}
|
||||
|
||||
let mut background = background;
|
||||
background.a *= if hover && opacity < 1.0 {
|
||||
@@ -193,9 +230,15 @@ fn styled_button(
|
||||
opacity
|
||||
};
|
||||
|
||||
let fill = if selected {
|
||||
let fill = if selected && !navigation {
|
||||
p.selected_background(
|
||||
if hover { 0.24 } else { 0.17 },
|
||||
if pressed {
|
||||
0.20
|
||||
} else if hover {
|
||||
0.16
|
||||
} else {
|
||||
0.10
|
||||
},
|
||||
background.a,
|
||||
on,
|
||||
background,
|
||||
@@ -211,11 +254,23 @@ fn styled_button(
|
||||
text_color: Some(on),
|
||||
icon_color: Some(on),
|
||||
border_radius: p.radius.min(9.).into(),
|
||||
border_width: if selected || !navigation { 1. } else { 0. },
|
||||
border_width: if !navigation || (selected && p.high_contrast) {
|
||||
1.
|
||||
} else {
|
||||
0.
|
||||
},
|
||||
border_color: if selected {
|
||||
p.accent
|
||||
if p.high_contrast {
|
||||
p.accent
|
||||
} else {
|
||||
mix(background, p.accent, 0.40)
|
||||
}
|
||||
} else if !navigation {
|
||||
mix(p.card, p.text, 0.18)
|
||||
mix(
|
||||
background,
|
||||
p.text,
|
||||
if p.high_contrast { 0.50 } else { 0.10 },
|
||||
)
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
@@ -227,10 +282,17 @@ fn styled_button(
|
||||
};
|
||||
|
||||
theme::Button::Custom {
|
||||
active: Box::new(move |focused, _| style(false, focused)),
|
||||
hovered: Box::new(move |focused, _| style(true, focused)),
|
||||
pressed: Box::new(move |focused, _| style(true, focused)),
|
||||
disabled: Box::new(move |_| style(false, false)),
|
||||
active: Box::new(move |focused, _| style(false, focused, false)),
|
||||
hovered: Box::new(move |focused, _| style(true, focused, false)),
|
||||
pressed: Box::new(move |focused, _| style(true, focused, true)),
|
||||
disabled: Box::new(move |_| {
|
||||
let mut disabled = style(false, false, false);
|
||||
if !selected {
|
||||
disabled.text_color = Some(p.muted);
|
||||
disabled.icon_color = Some(p.muted);
|
||||
}
|
||||
disabled
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +495,45 @@ pub fn filled_button(fill: Color, foreground: Color, radius: f32, opacity: f32)
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn navigation_selection_is_quiet_but_keyboard_focus_and_press_are_explicit() {
|
||||
use cosmic::widget::button::Catalog;
|
||||
for preset in ferese_config::presets::PRESETS {
|
||||
let mut resolved = ferese_config::theme::default_theme();
|
||||
resolved.appearance = preset.appearance;
|
||||
resolved.tokens = ferese_config::theme::preset(preset.id, preset.appearance).unwrap();
|
||||
for high_contrast in [false, true] {
|
||||
resolved.accessibility.increase_contrast = high_contrast;
|
||||
let palette = Palette::from_resolved(&resolved);
|
||||
let native = palette.native_theme();
|
||||
for selected in [false, true] {
|
||||
let class = navigation_style(palette, selected);
|
||||
let resting = native.active(false, selected, &class);
|
||||
let focus = native.active(true, selected, &class);
|
||||
assert_eq!(resting.outline_width, 0.);
|
||||
assert_eq!(focus.outline_width, 2.);
|
||||
assert_eq!(
|
||||
resting.border_width,
|
||||
if selected && high_contrast { 1. } else { 0. }
|
||||
);
|
||||
let Some(Background::Color(fill)) = resting.background else {
|
||||
panic!("navigation should not be a gradient")
|
||||
};
|
||||
assert!(
|
||||
crate::contrast(fill, resting.text_color.unwrap())
|
||||
>= if high_contrast { 7. } else { 4.5 },
|
||||
"{}",
|
||||
preset.id
|
||||
);
|
||||
assert_ne!(
|
||||
native.hovered(false, selected, &class).background,
|
||||
native.pressed(false, selected, &class).background
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hiding_modal_focus_preserves_button_borders_and_materials() {
|
||||
use cosmic::widget::button::Catalog;
|
||||
|
||||
Reference in new issue
Block a user