Cosmic advanced text (#103)
* wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
This commit is contained in:
parent
a173794bed
commit
e056e8c830
65 changed files with 3431 additions and 405 deletions
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
libcosmic = { path = "../..", default-features = false, features = ["wayland", "tokio"] }
|
||||
libcosmic = { path = "../..", default-features = false, features = ["wayland", "tokio", "tiny_skia", "a11y"] }
|
||||
|
|
|
|||
|
|
@ -2,19 +2,18 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use cosmic::{
|
||||
iced::{self, wayland::window::set_mode_window, Alignment, Application, Command, Length},
|
||||
iced::{self, wayland::window::set_mode_window, Application, Command, Length},
|
||||
iced::{
|
||||
wayland::window::{start_drag_window, toggle_maximize},
|
||||
widget::{
|
||||
column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
||||
},
|
||||
widget::{column, container, horizontal_space, pick_list, progress_bar, row, slider},
|
||||
window, Color,
|
||||
},
|
||||
iced_native::window,
|
||||
iced_style::application,
|
||||
theme::{self, Theme},
|
||||
widget::{
|
||||
button, header_bar, nav_bar, nav_bar_toggle,
|
||||
rectangle_tracker::{rectangle_tracker_subscription, RectangleTracker, RectangleUpdate},
|
||||
scrollable, segmented_button, settings, toggler, IconSource,
|
||||
scrollable, segmented_button, segmented_selection, settings, toggler, IconSource,
|
||||
},
|
||||
Element, ElementExt,
|
||||
};
|
||||
|
|
@ -118,6 +117,7 @@ pub struct Window {
|
|||
show_maximize: bool,
|
||||
exit: bool,
|
||||
rectangle_tracker: Option<RectangleTracker<u32>>,
|
||||
pub selection: segmented_button::SingleSelectModel,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
|
@ -176,6 +176,8 @@ pub enum Message {
|
|||
InputChanged,
|
||||
Rectangle(RectangleUpdate<u32>),
|
||||
NavBar(segmented_button::Entity),
|
||||
Ignore,
|
||||
Selection(segmented_button::Entity),
|
||||
}
|
||||
|
||||
impl Application for Window {
|
||||
|
|
@ -189,6 +191,11 @@ impl Application for Window {
|
|||
.nav_bar_toggled(true)
|
||||
.show_maximize(true)
|
||||
.show_minimize(true);
|
||||
window.selection = segmented_button::Model::builder()
|
||||
.insert(|b| b.text("Choice A").activate())
|
||||
.insert(|b| b.text("Choice B"))
|
||||
.insert(|b| b.text("Choice C"))
|
||||
.build();
|
||||
window.slider_value = 50.0;
|
||||
// window.theme = Theme::Light;
|
||||
window.pick_list_selected = Some("Option 1");
|
||||
|
|
@ -240,9 +247,9 @@ impl Application for Window {
|
|||
Message::ToggleNavBarCondensed => {
|
||||
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed
|
||||
}
|
||||
Message::Drag => return start_drag_window(window::Id::new(0)),
|
||||
Message::Minimize => return set_mode_window(window::Id::new(0), window::Mode::Hidden),
|
||||
Message::Maximize => return toggle_maximize(window::Id::new(0)),
|
||||
Message::Drag => return start_drag_window(window::Id(0)),
|
||||
Message::Minimize => return set_mode_window(window::Id(0), window::Mode::Hidden),
|
||||
Message::Maximize => return toggle_maximize(window::Id(0)),
|
||||
Message::RowSelected(row) => println!("Selected row {row}"),
|
||||
Message::InputChanged => {}
|
||||
Message::Rectangle(r) => match r {
|
||||
|
|
@ -253,6 +260,8 @@ impl Application for Window {
|
|||
self.rectangle_tracker.replace(t);
|
||||
}
|
||||
},
|
||||
Message::Ignore => {}
|
||||
Message::Selection(key) => self.selection.activate(key),
|
||||
}
|
||||
|
||||
Command::none()
|
||||
|
|
@ -302,7 +311,7 @@ impl Application for Window {
|
|||
widgets.push(nav_bar.debug(self.debug));
|
||||
}
|
||||
|
||||
if !(self.is_condensed() && nav_bar_toggled) {
|
||||
if !nav_bar_toggled {
|
||||
let secondary = button(ButtonTheme::Secondary)
|
||||
.text("Secondary")
|
||||
.on_press(Message::ButtonPressed);
|
||||
|
|
@ -363,20 +372,23 @@ impl Application for Window {
|
|||
.add(settings::item(
|
||||
"Slider",
|
||||
slider(0.0..=100.0, self.slider_value, Message::SliderChanged)
|
||||
.width(Length::Units(250)),
|
||||
.width(Length::Fixed(250.0)),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Progress",
|
||||
progress_bar(0.0..=100.0, self.slider_value)
|
||||
.width(Length::Units(250))
|
||||
.height(Length::Units(4)),
|
||||
.width(Length::Fixed(250.0))
|
||||
.height(Length::Fixed(4.0)),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Segmented Button",
|
||||
segmented_selection::horizontal(&self.selection)
|
||||
.on_activate(Message::Selection),
|
||||
))
|
||||
.into(),
|
||||
])
|
||||
.into();
|
||||
|
||||
let mut widgets: Vec<Element<_>> = Vec::with_capacity(2);
|
||||
|
||||
widgets.push(
|
||||
scrollable(row![
|
||||
horizontal_space(Length::Fill),
|
||||
|
|
@ -391,6 +403,7 @@ impl Application for Window {
|
|||
.padding([0, 8, 8, 8])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(theme::Container::Background)
|
||||
.into();
|
||||
|
||||
column(vec![header, content]).into()
|
||||
|
|
@ -408,6 +421,13 @@ impl Application for Window {
|
|||
Message::Close
|
||||
}
|
||||
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
||||
rectangle_tracker_subscription(0).map(|(i, e)| Message::Rectangle(e))
|
||||
rectangle_tracker_subscription(0).map(|(_, e)| Message::Rectangle(e))
|
||||
}
|
||||
|
||||
fn style(&self) -> <Self::Theme as cosmic::iced_style::application::StyleSheet>::Style {
|
||||
cosmic::theme::Application::Custom(Box::new(|theme| application::Appearance {
|
||||
background_color: Color::TRANSPARENT,
|
||||
text_color: theme.cosmic().on_bg_color().into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ publish = false
|
|||
[dependencies]
|
||||
apply = "0.3.0"
|
||||
fraction = "0.13.0"
|
||||
libcosmic = { path = "../..", default-features = false, features = ["debug", "winit_softbuffer"] }
|
||||
libcosmic = { path = "../..", default-features = false, features = ["debug", "winit_tiny_skia", "a11y"] }
|
||||
once_cell = "1.15"
|
||||
slotmap = "1.0.6"
|
||||
slotmap = "1.0.6"
|
||||
env_logger = "0.10"
|
||||
log = "0.4.17"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,15 @@
|
|||
use cosmic::{iced::Application, settings};
|
||||
|
||||
mod window;
|
||||
use env_logger::Env;
|
||||
pub use window::*;
|
||||
|
||||
pub fn main() -> cosmic::iced::Result {
|
||||
let env = Env::default()
|
||||
.filter_or("MY_LOG_LEVEL", "info")
|
||||
.write_style_or("MY_LOG_STYLE", "always");
|
||||
|
||||
env_logger::init_from_env(env);
|
||||
settings::set_default_icon_theme("Pop");
|
||||
let mut settings = settings();
|
||||
settings.window.min_size = Some((600, 300));
|
||||
|
|
|
|||
|
|
@ -1,25 +1,34 @@
|
|||
/// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
use cosmic::{
|
||||
iced::widget::{self, button, column, container, horizontal_space, row, text},
|
||||
cosmic_config::config_subscription,
|
||||
font::load_fonts,
|
||||
iced::{self, Application, Command, Length, Subscription},
|
||||
iced_native::{subscription, window},
|
||||
iced_winit::window::{close, drag, minimize, toggle_maximize},
|
||||
iced::{
|
||||
subscription,
|
||||
widget::{self, column, container, horizontal_space, row, text},
|
||||
window::{self, close, drag, minimize, toggle_maximize},
|
||||
},
|
||||
keyboard_nav,
|
||||
theme::{self, Theme, COSMIC_DARK, COSMIC_LIGHT},
|
||||
theme::{self, CosmicTheme, CosmicThemeCss, Theme},
|
||||
widget::{
|
||||
header_bar, icon, list, nav_bar, nav_bar_toggle, scrollable, segmented_button, settings,
|
||||
warning, IconSource,
|
||||
},
|
||||
Element, ElementExt,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use log::error;
|
||||
use std::{
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
borrow::Cow,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc,
|
||||
},
|
||||
vec,
|
||||
};
|
||||
|
||||
static BTN: Lazy<button::Id> = Lazy::new(button::Id::unique);
|
||||
// XXX The use of button is removed because it assigns the same ID to multiple buttons, causing a crash when a11y is enabled...
|
||||
// static BTN: Lazy<id::Id> = Lazy::new(|| id::Id::new("BTN"));
|
||||
|
||||
mod bluetooth;
|
||||
|
||||
|
|
@ -151,6 +160,7 @@ pub struct Window {
|
|||
warning_message: String,
|
||||
scale_factor: f64,
|
||||
scale_factor_string: String,
|
||||
system_theme: Arc<CosmicTheme>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
|
@ -194,6 +204,8 @@ pub enum Message {
|
|||
ToggleNavBar,
|
||||
ToggleNavBarCondensed,
|
||||
ToggleWarning,
|
||||
FontsLoaded,
|
||||
SystemTheme(CosmicTheme),
|
||||
}
|
||||
|
||||
impl From<Page> for Message {
|
||||
|
|
@ -237,7 +249,7 @@ impl Window {
|
|||
))
|
||||
.padding(0)
|
||||
.style(theme::Button::Link)
|
||||
.id(BTN.clone())
|
||||
// .id(BTN.clone())
|
||||
.on_press(Message::from(page)),
|
||||
row!(
|
||||
text(sub_page.title()).size(30),
|
||||
|
|
@ -282,7 +294,7 @@ impl Window {
|
|||
.padding(0)
|
||||
.style(theme::Button::Transparent)
|
||||
.on_press(Message::from(sub_page.into_page()))
|
||||
.id(BTN.clone())
|
||||
// .id(BTN.clone())
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +353,7 @@ impl Application for Window {
|
|||
window.insert_page(Page::Accessibility);
|
||||
window.insert_page(Page::Applications);
|
||||
|
||||
(window, Command::none())
|
||||
(window, load_fonts().map(|_| Message::FontsLoaded))
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
|
|
@ -371,6 +383,16 @@ impl Application for Window {
|
|||
Subscription::batch(vec![
|
||||
window_break.map(|_| Message::CondensedViewToggle),
|
||||
keyboard_nav::subscription().map(Message::KeyboardNav),
|
||||
config_subscription::<_, CosmicThemeCss>(0, Cow::from("com.system76.CosmicTheme"), 1)
|
||||
.map(|(_, update)| match update {
|
||||
Ok(t) => Message::SystemTheme(t.into_srgba()),
|
||||
Err((errors, t)) => {
|
||||
for error in errors {
|
||||
error!("{:?}", error);
|
||||
}
|
||||
Message::SystemTheme(t.into_srgba())
|
||||
}
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -391,7 +413,13 @@ impl Application for Window {
|
|||
Some(demo::Output::Debug(debug)) => self.debug = debug,
|
||||
Some(demo::Output::ScalingFactor(factor)) => self.set_scale_factor(factor),
|
||||
Some(demo::Output::ThemeChanged(theme)) => {
|
||||
self.theme = theme;
|
||||
self.theme = match theme {
|
||||
demo::ThemeVariant::Light => Theme::light(),
|
||||
demo::ThemeVariant::Dark => Theme::dark(),
|
||||
demo::ThemeVariant::HighContrastDark => Theme::dark_hc(),
|
||||
demo::ThemeVariant::HighContrastLight => Theme::light_hc(),
|
||||
demo::ThemeVariant::Custom => Theme::custom(self.system_theme.clone()),
|
||||
};
|
||||
}
|
||||
Some(demo::Output::ToggleWarning) => self.toggle_warning(),
|
||||
None => (),
|
||||
|
|
@ -405,10 +433,10 @@ impl Application for Window {
|
|||
Message::ToggleNavBarCondensed => {
|
||||
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed
|
||||
}
|
||||
Message::Drag => return drag(window::Id::new(0)),
|
||||
Message::Close => return close(window::Id::new(0)),
|
||||
Message::Minimize => return minimize(window::Id::new(0), true),
|
||||
Message::Maximize => return toggle_maximize(window::Id::new(0)),
|
||||
Message::Drag => return drag(),
|
||||
Message::Close => return close(),
|
||||
Message::Minimize => return minimize(true),
|
||||
Message::Maximize => return toggle_maximize(),
|
||||
|
||||
Message::InputChanged => {}
|
||||
|
||||
|
|
@ -420,6 +448,10 @@ impl Application for Window {
|
|||
_ => (),
|
||||
},
|
||||
Message::ToggleWarning => self.toggle_warning(),
|
||||
Message::FontsLoaded => {}
|
||||
Message::SystemTheme(t) => {
|
||||
self.system_theme = Arc::new(t);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
|
@ -545,17 +577,21 @@ impl Application for Window {
|
|||
.padding([0, 8, 8, 8])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(theme::Container::Background)
|
||||
.into();
|
||||
let warning = warning(&self.warning_message)
|
||||
.on_close(Message::ToggleWarning)
|
||||
.into();
|
||||
if self.show_warning {
|
||||
column(vec![
|
||||
column![
|
||||
header,
|
||||
warning,
|
||||
iced::widget::vertical_space(Length::Units(12)).into(),
|
||||
content,
|
||||
])
|
||||
container(column(vec![
|
||||
warning,
|
||||
iced::widget::vertical_space(Length::Fixed(12.0)).into(),
|
||||
content,
|
||||
]))
|
||||
.style(theme::Container::Background)
|
||||
]
|
||||
.into()
|
||||
} else {
|
||||
column(vec![header, content]).into()
|
||||
|
|
@ -567,6 +603,6 @@ impl Application for Window {
|
|||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
self.theme
|
||||
self.theme.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use apply::Apply;
|
||||
use cosmic::{
|
||||
cosmic_theme,
|
||||
iced::widget::{checkbox, pick_list, progress_bar, radio, row, slider, text, text_input},
|
||||
iced::{Alignment, Length},
|
||||
theme::{self, Button as ButtonTheme, Theme},
|
||||
iced::widget::{checkbox, column, pick_list, progress_bar, radio, slider, text, text_input},
|
||||
iced::{id, Alignment, Length},
|
||||
theme::{self, Button as ButtonTheme, Theme, ThemeType},
|
||||
widget::{
|
||||
button, container, icon, segmented_button, segmented_selection, settings, spin_button,
|
||||
toggler, view_switcher,
|
||||
|
|
@ -15,6 +15,33 @@ use once_cell::sync::Lazy;
|
|||
|
||||
use super::{Page, Window};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)]
|
||||
pub enum ThemeVariant {
|
||||
Light,
|
||||
Dark,
|
||||
HighContrastDark,
|
||||
HighContrastLight,
|
||||
Custom,
|
||||
}
|
||||
|
||||
impl From<&ThemeType> for ThemeVariant {
|
||||
fn from(theme: &ThemeType) -> Self {
|
||||
match theme {
|
||||
ThemeType::Light => ThemeVariant::Light,
|
||||
ThemeType::Dark => ThemeVariant::Dark,
|
||||
ThemeType::HighContrastDark => ThemeVariant::HighContrastDark,
|
||||
ThemeType::HighContrastLight => ThemeVariant::HighContrastLight,
|
||||
ThemeType::Custom(_) => ThemeVariant::Custom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThemeType> for ThemeVariant {
|
||||
fn from(theme: ThemeType) -> Self {
|
||||
ThemeVariant::from(&theme)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DemoView {
|
||||
TabA,
|
||||
TabB,
|
||||
|
|
@ -29,7 +56,7 @@ pub enum MultiOption {
|
|||
OptionD,
|
||||
OptionE,
|
||||
}
|
||||
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
|
||||
static INPUT_ID: Lazy<id::Id> = Lazy::new(id::Id::unique);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
|
|
@ -44,7 +71,7 @@ pub enum Message {
|
|||
Selection(segmented_button::Entity),
|
||||
SliderChanged(f32),
|
||||
SpinButton(spin_button::Message),
|
||||
ThemeChanged(Theme),
|
||||
ThemeChanged(ThemeVariant),
|
||||
ToggleWarning,
|
||||
TogglerToggled(bool),
|
||||
ViewSwitcher(segmented_button::Entity),
|
||||
|
|
@ -54,7 +81,7 @@ pub enum Message {
|
|||
pub enum Output {
|
||||
Debug(bool),
|
||||
ScalingFactor(f32),
|
||||
ThemeChanged(Theme),
|
||||
ThemeChanged(ThemeVariant),
|
||||
ToggleWarning,
|
||||
}
|
||||
|
||||
|
|
@ -151,20 +178,21 @@ impl State {
|
|||
|
||||
pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||
let choose_theme = [
|
||||
Theme::light(),
|
||||
Theme::dark(),
|
||||
Theme::light_hc(),
|
||||
Theme::dark_hc(),
|
||||
ThemeVariant::Light,
|
||||
ThemeVariant::Dark,
|
||||
ThemeVariant::HighContrastLight,
|
||||
ThemeVariant::HighContrastLight,
|
||||
ThemeVariant::Custom,
|
||||
]
|
||||
.iter()
|
||||
.into_iter()
|
||||
.fold(
|
||||
row![].spacing(10).align_items(Alignment::Center),
|
||||
column![].spacing(10).align_items(Alignment::Center),
|
||||
|row, theme| {
|
||||
row.push(radio(
|
||||
format!("{:?}", theme.theme_type),
|
||||
*theme,
|
||||
if window.theme == *theme {
|
||||
Some(*theme)
|
||||
format!("{:?}", theme),
|
||||
theme,
|
||||
if ThemeVariant::from(&window.theme.theme_type) == theme {
|
||||
Some(theme)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
|
@ -253,13 +281,14 @@ impl State {
|
|||
.add(settings::item(
|
||||
"Slider",
|
||||
slider(0.0..=100.0, self.slider_value, Message::SliderChanged)
|
||||
.width(Length::Units(250)),
|
||||
.width(Length::Fixed(250.0))
|
||||
.height(38),
|
||||
))
|
||||
.add(settings::item(
|
||||
"Progress",
|
||||
progress_bar(0.0..=100.0, self.slider_value)
|
||||
.width(Length::Units(250))
|
||||
.height(Length::Units(4)),
|
||||
.width(Length::Fixed(250.0))
|
||||
.height(Length::Fixed(4.0)),
|
||||
))
|
||||
.add(settings::item_row(vec![checkbox(
|
||||
"Checkbox",
|
||||
|
|
@ -401,8 +430,8 @@ impl State {
|
|||
text_input(
|
||||
"Type to search apps or type “?” for more options...",
|
||||
&self.entry_value,
|
||||
Message::InputChanged,
|
||||
)
|
||||
.on_input(Message::InputChanged)
|
||||
// .on_submit(Message::Activate(None))
|
||||
.padding(8)
|
||||
.size(20)
|
||||
|
|
|
|||
|
|
@ -219,10 +219,10 @@ impl State {
|
|||
for image_path in chunk.iter() {
|
||||
image_row.push(if image_path.ends_with(".svg") {
|
||||
svg(svg::Handle::from_path(image_path))
|
||||
.width(Length::Units(150))
|
||||
.width(Length::Fixed(150.0))
|
||||
.into()
|
||||
} else {
|
||||
image(image_path).width(Length::Units(150)).into()
|
||||
image(image_path).width(Length::Fixed(150.0)).into()
|
||||
});
|
||||
}
|
||||
image_column.push(row(image_row).spacing(16).into());
|
||||
|
|
@ -234,7 +234,7 @@ impl State {
|
|||
horizontal_space(Length::Fill),
|
||||
container(
|
||||
image("/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png")
|
||||
.width(Length::Units(300))
|
||||
.width(Length::Fixed(300.0))
|
||||
)
|
||||
.padding(4)
|
||||
.style(theme::Container::Background),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use apply::Apply;
|
||||
use cosmic::iced::widget::{horizontal_space, row, scrollable};
|
||||
use cosmic::iced::Length;
|
||||
use cosmic::iced_winit::Alignment;
|
||||
use cosmic::iced::{Alignment, Length};
|
||||
use cosmic::widget::{button, segmented_button, view_switcher};
|
||||
use cosmic::{theme, Element};
|
||||
use slotmap::Key;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue