Files
feresaria/shell/ferese-settings/src/panel_inspector.rs
T

435 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::panel_edit::{Action, Destination, Zone};
use crate::{App, Element, Field, Kind, Message, widget};
use cosmic::iced::{Alignment, Length};
use ferese_panel::{Group, Item, ItemKind, Panel, Representation};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Choice(&'static str, &'static str);
impl std::fmt::Display for Choice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.1)
}
}
impl App {
fn inspector_heading(&self, title: &str, subtitle: &str) -> Element<'static, Message> {
widget::column([])
.spacing(5)
.push(
widget::row([])
.align_y(Alignment::Center)
.push(
self.panel_heading(title.to_owned(), 16.)
.width(Length::Fill),
)
.push(self.settings_icon_button(
"Deselect",
"M6 6l12 12 M6 18L18 6",
Some(Message::PanelClearSelection),
)),
)
.push(self.note(subtitle))
.push(self.panel_divider())
.into()
}
pub(super) fn panel_divider(&self) -> Element<'static, Message> {
let p = crate::visuals::Palette::from_resolved(&self.resolved.presented);
widget::container(widget::Space::new().height(1).width(Length::Fill))
.class(crate::visuals::surface(
ferese_theme::mix(p.card, p.text, 0.09),
0.,
))
.into()
}
pub(super) fn item_inspector(
&self,
panel: &Panel,
item: &Item,
destination: Destination,
path: String,
) -> Element<'static, Message> {
let palette = crate::visuals::Palette::from_resolved(&self.resolved.presented);
let mut rows = widget::column([])
.spacing(12)
.push(self.inspector_heading(item.kind.label(), "Item settings"));
let item_id = item.id.clone();
rows = rows.push(
widget::column([])
.spacing(8)
.push(self.label("Placement", 13.))
.push(crate::visuals::panel_select(
cosmic::iced::widget::pick_list(
crate::panel_edit::destinations(panel),
Some(destination.clone()),
move |destination| {
Message::PanelEdit(Action::Move(item_id.clone(), destination))
},
)
.font(self.font)
.text_size(13)
.padding([8, 10])
.width(Length::Fill),
palette,
)),
);
rows = rows.push(self.panel_field(
item,
Field::new(
format!("{path}.visible"),
"Show item",
"",
Kind::Toggle(true),
),
));
rows = rows.push(self.panel_divider());
if matches!(item.kind, ItemKind::Workspaces { .. }) {
rows = rows.push(self.workspace_style_picker(item));
rows = rows.push(self.panel_divider());
}
rows = rows.push(self.panel_field(
item,
Field::new(
format!("{path}.overflow"),
"Overflow",
"Choose when to use the overflow menu.",
Kind::Choice {
default: "auto",
choices: &[
("never", "Keep visible"),
("auto", "When needed"),
("always", "Always"),
],
},
),
));
let representations = item.kind.representations();
if representations.len() > 1 {
let choices: &'static [(&'static str, &'static str)] = match representations {
[Representation::Wide, Representation::Icon] => {
&[("wide", "Full"), ("icon", "Icon")]
}
_ => &[("wide", "Full"), ("compact", "Compact"), ("icon", "Icon")],
};
rows = rows.push(self.panel_field(
item,
Field::new(
format!("{path}.representation"),
"Preferred size",
"Adapts to available space.",
Kind::Choice {
default: "wide",
choices,
},
),
));
}
if matches!(item.kind, ItemKind::Battery { .. }) {
rows = rows.push(self.panel_field(
item,
Field::new(
format!("{path}.percentage"),
"Battery percentage",
"",
Kind::Toggle(true),
),
));
}
let group = Zone::ALL
.into_iter()
.flat_map(|zone| &zone.definition(panel).groups)
.find(|group| group.id == destination.group)
.expect("the inspector selection refers to an item or group in the current panel");
let index = group
.items
.iter()
.position(|candidate| candidate.id == item.id)
.expect("the inspector selection refers to an item or group in the current panel");
rows.push(self.panel_divider())
.push(
self.inspector_actions(
(index > 0).then(|| Message::PanelEdit(Action::Earlier(item.id.clone()))),
(index + 1 < group.items.len())
.then(|| Message::PanelEdit(Action::Later(item.id.clone()))),
"Remove item",
Some(Message::PanelEdit(Action::Remove(item.id.clone()))),
),
)
.into()
}
fn workspace_style_picker(&self, item: &Item) -> Element<'static, Message> {
let palette = crate::visuals::Palette::from_resolved(&self.resolved.presented);
let ItemKind::Workspaces { style: selected } = item.kind else {
unreachable!()
};
let mut choices = widget::column([])
.spacing(6)
.push(self.label("Workspace style", 13.))
.push(self.note("A sample of active, occupied and empty workspaces."));
for style in ferese_panel::WorkspaceStyle::ALL {
let sample = ferese_theme::workspaces::sample(style, palette, self.font, 22.);
choices = choices.push(
widget::button::custom(
widget::row![self.label(style.label(), 12.).width(Length::Fill), sample,]
.spacing(10)
.align_y(Alignment::Center),
)
.id(format!("workspace-style:{}", style.key()).into())
.name(format!("Workspace style: {}", style.label()))
.padding([8, 10])
.width(Length::Fill)
.class(crate::visuals::panel_button(palette, style == selected))
.on_press_maybe((style != selected).then(|| {
Message::PanelEdit(Action::Set(
item.id.clone(),
"style".into(),
style.key().into(),
))
})),
);
}
choices.into()
}
pub(super) fn group_inspector(
&self,
panel: &Panel,
group: &Group,
placement: Zone,
) -> Element<'static, Message> {
let palette = crate::visuals::Palette::from_resolved(&self.resolved.presented);
let id = group.id.clone();
let mut rows = widget::column([])
.spacing(12)
.push(self.inspector_heading(
&crate::panel_controls::group_name(&group.id.0),
"Group arrangement & spacing",
))
.push(
widget::column([])
.spacing(8)
.push(self.label("Placement", 13.))
.push(crate::visuals::panel_select(
cosmic::iced::widget::pick_list(
Zone::ALL.to_vec(),
Some(placement),
move |zone| Message::PanelEdit(Action::MoveGroup(id.clone(), zone)),
)
.font(self.font)
.text_size(13)
.padding([8, 10])
.width(Length::Fill),
palette,
)),
);
rows = rows.push(self.panel_divider());
let mut spacing = widget::column([]).spacing(12);
for (name, label, value, maximum) in [
("spacing", "Item spacing", group.spacing, 64.),
(
"padding_vertical",
"Vertical padding",
f32::from(group.padding[0]),
32.,
),
(
"padding_horizontal",
"Horizontal padding",
f32::from(group.padding[1]),
32.,
),
(
"island_padding",
"Island padding",
group.island_padding,
32.,
),
] {
if panel.group_surface() != ferese_panel::GroupSurface::Island
&& name == "island_padding"
{
continue;
}
let change = |value: f32| {
let value = if name.starts_with("padding_") {
serde_json::json!(value as u16)
} else {
serde_json::json!(value)
};
Message::PanelEdit(Action::SetGroup(group.id.clone(), name.into(), value))
};
spacing = spacing.push(
widget::row([])
.spacing(8)
.align_y(Alignment::Center)
.push(self.label(label, 13.).width(Length::Fill))
.push(
widget::row([])
.spacing(4)
.align_y(Alignment::Center)
.push(self.settings_icon_button(
&format!("Decrease {label}"),
"M5 12h14",
(value > 0.).then(|| change((value - 1.).max(0.))),
))
.push(
self.label(format!("{value} px"), 12.)
.width(48)
.align_x(Alignment::Center),
)
.push(self.settings_icon_button(
&format!("Increase {label}"),
"M12 5v14 M5 12h14",
(value < maximum).then(|| change((value + 1.).min(maximum))),
)),
),
);
}
rows = rows.push(spacing).push(self.panel_divider());
let index = placement
.definition(panel)
.groups
.iter()
.position(|candidate| candidate.id == group.id)
.expect("the inspector selection refers to an item or group in the current panel");
rows = rows.push(
self.inspector_actions(
(index > 0).then(|| Message::PanelEdit(Action::EarlierGroup(group.id.clone()))),
(index + 1 < placement.definition(panel).groups.len())
.then(|| Message::PanelEdit(Action::LaterGroup(group.id.clone()))),
"Remove group",
group
.items
.is_empty()
.then(|| Message::PanelEdit(Action::RemoveGroup(group.id.clone()))),
),
);
if !group.items.is_empty() {
rows = rows
.push(self.note("Move or remove this group’s items before removing the group."));
}
rows.into()
}
fn inspector_actions(
&self,
earlier: Option<Message>,
later: Option<Message>,
remove_label: &str,
remove: Option<Message>,
) -> Element<'static, Message> {
widget::row([])
.spacing(8)
.push(self.settings_icon_button("Up", "M6 15l6-6 6 6", earlier))
.push(self.settings_icon_button("Down", "M6 9l6 6 6-6", later))
.push(self.settings_icon_button(
remove_label,
"M4 6h16 M9 6V3h6v3 M6 6l1 15h10l1-15 M10 10v7 M14 10v7",
remove,
))
.into()
}
fn panel_field(&self, item: &Item, field: Field) -> Element<'static, Message> {
let p = crate::visuals::Palette::from_resolved(&self.resolved.presented);
let id = item.id.clone();
let name = field
.path
.rsplit('.')
.next()
.expect("splitting a field path always yields its final component")
.to_owned();
let values = serde_json::to_value(item).expect("serializable panel item");
match field.kind {
Kind::Choice { default, choices } => {
let value = values
.get(&name)
.and_then(serde_json::Value::as_str)
.unwrap_or(default);
if name == "overflow" {
let selected = choices
.iter()
.find(|(key, _)| *key == value)
.map(|&(key, label)| Choice(key, label));
return widget::column([])
.spacing(8)
.push(self.label(field.label, 13.))
.push(crate::visuals::panel_select(
cosmic::iced::widget::pick_list(
choices
.iter()
.map(|&(key, label)| Choice(key, label))
.collect::<Vec<_>>(),
selected,
move |choice| {
Message::PanelEdit(Action::Set(
id.clone(),
name.clone(),
choice.0.into(),
))
},
)
.font(self.font)
.text_size(13)
.padding([8, 10])
.width(Length::Fill),
p,
))
.push(self.note(&field.description))
.into();
}
let options = widget::row(choices.iter().map(|&(key, label)| {
widget::button::custom(self.label(label, 12.))
.name(format!("{}: {label}", field.label))
.padding([8, 4])
.width(Length::Fill)
.class(crate::visuals::panel_button(p, value == key))
.on_press(Message::PanelEdit(Action::Set(
id.clone(),
name.clone(),
key.into(),
)))
.into()
}))
.spacing(2)
.width(Length::Fill);
let mut rows = widget::column([])
.spacing(8)
.push(self.label(field.label, 13.))
.push(
widget::container(options)
.padding(3)
.class(crate::visuals::surface(p.sidebar, 9.)),
);
if !field.description.is_empty() {
rows = rows.push(self.note(&field.description));
}
rows.into()
}
Kind::Toggle(default) => {
let enabled = values
.get(&name)
.and_then(serde_json::Value::as_bool)
.unwrap_or(default);
widget::row([])
.spacing(12)
.align_y(Alignment::Center)
.push(self.label(field.label.clone(), 13.).width(Length::Fill))
.push(
ferese_theme::controls::switch(enabled, p)
.name(format!(
"{}: {}",
field.label,
if enabled { "on" } else { "off" }
))
.on_press(Message::PanelEdit(Action::Set(id, name, (!enabled).into()))),
)
.into()
}
_ => unreachable!("panel inspector fields are choices or toggles"),
}
}
}