chore: apply cargo fmt
This commit is contained in:
parent
cb2b0f7b9a
commit
75687acf2f
22 changed files with 373 additions and 285 deletions
|
|
@ -2,18 +2,26 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced_native::window,
|
iced::{
|
||||||
iced::{widget::{
|
self,
|
||||||
column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
wayland::{window::set_mode_window, SurfaceIdWrapper},
|
||||||
}, wayland::window::{start_drag_window, toggle_maximize}},
|
Alignment, Application, Command, Length,
|
||||||
iced::{self, wayland::{SurfaceIdWrapper, window::set_mode_window}, Alignment, Application, Command, Length},
|
},
|
||||||
|
iced::{
|
||||||
|
wayland::window::{start_drag_window, toggle_maximize},
|
||||||
|
widget::{
|
||||||
|
column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
||||||
|
},
|
||||||
|
},
|
||||||
iced_lazy::responsive,
|
iced_lazy::responsive,
|
||||||
|
iced_native::window,
|
||||||
theme::{self, Theme},
|
theme::{self, Theme},
|
||||||
widget::{
|
widget::{
|
||||||
button, header_bar, nav_bar, nav_bar_page, nav_bar_section, nav_button, scrollable,
|
button, header_bar, nav_bar, nav_bar_page, nav_bar_section, nav_button,
|
||||||
settings, toggler, rectangle_tracker::{RectangleTracker, rectangle_tracker_subscription, RectangleUpdate},
|
rectangle_tracker::{rectangle_tracker_subscription, RectangleTracker, RectangleUpdate},
|
||||||
|
scrollable, settings, toggler,
|
||||||
},
|
},
|
||||||
Element, ElementExt
|
Element, ElementExt,
|
||||||
};
|
};
|
||||||
use std::{collections::BTreeMap, vec};
|
use std::{collections::BTreeMap, vec};
|
||||||
use theme::Button as ButtonTheme;
|
use theme::Button as ButtonTheme;
|
||||||
|
|
@ -70,7 +78,7 @@ pub enum Message {
|
||||||
Minimize,
|
Minimize,
|
||||||
Maximize,
|
Maximize,
|
||||||
InputChanged,
|
InputChanged,
|
||||||
Rectangle(RectangleUpdate<u32>)
|
Rectangle(RectangleUpdate<u32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Window {
|
impl Application for Window {
|
||||||
|
|
@ -115,8 +123,12 @@ impl Application for Window {
|
||||||
Message::RowSelected(row) => println!("Selected row {row}"),
|
Message::RowSelected(row) => println!("Selected row {row}"),
|
||||||
Message::InputChanged => {}
|
Message::InputChanged => {}
|
||||||
Message::Rectangle(r) => match r {
|
Message::Rectangle(r) => match r {
|
||||||
RectangleUpdate::Rectangle(r) => {dbg!(r);},
|
RectangleUpdate::Rectangle(r) => {
|
||||||
RectangleUpdate::Init(t) => {self.rectangle_tracker.replace(t);},
|
dbg!(r);
|
||||||
|
}
|
||||||
|
RectangleUpdate::Init(t) => {
|
||||||
|
self.rectangle_tracker.replace(t);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,8 +247,8 @@ impl Application for Window {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let secondary = button(ButtonTheme::Secondary)
|
let secondary = button(ButtonTheme::Secondary)
|
||||||
.text("Secondary")
|
.text("Secondary")
|
||||||
.on_press(Message::ButtonPressed);
|
.on_press(Message::ButtonPressed);
|
||||||
|
|
||||||
let secondary = if let Some(tracker) = self.rectangle_tracker.as_ref() {
|
let secondary = if let Some(tracker) = self.rectangle_tracker.as_ref() {
|
||||||
tracker.container(0, secondary).into()
|
tracker.container(0, secondary).into()
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,10 @@ impl Window {
|
||||||
self.page = page;
|
self.page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parent_page_button<Message: Clone + From<Page> + 'static>(&self, sub_page: impl SubPage) -> Element<Message> {
|
fn parent_page_button<Message: Clone + From<Page> + 'static>(
|
||||||
|
&self,
|
||||||
|
sub_page: impl SubPage,
|
||||||
|
) -> Element<Message> {
|
||||||
let page = sub_page.parent_page();
|
let page = sub_page.parent_page();
|
||||||
column!(
|
column!(
|
||||||
iced::widget::Button::new(row!(
|
iced::widget::Button::new(row!(
|
||||||
|
|
@ -208,7 +211,10 @@ impl Window {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sub_page_button<Message: Clone + From<Page> + 'static>(&self, sub_page: impl SubPage) -> Element<Message> {
|
fn sub_page_button<Message: Clone + From<Page> + 'static>(
|
||||||
|
&self,
|
||||||
|
sub_page: impl SubPage,
|
||||||
|
) -> Element<Message> {
|
||||||
iced::widget::Button::new(
|
iced::widget::Button::new(
|
||||||
container(
|
container(
|
||||||
settings::item_row(vec![
|
settings::item_row(vec![
|
||||||
|
|
@ -244,7 +250,10 @@ impl Window {
|
||||||
]).into()
|
]).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_unimplemented_sub_page<'a, Message: Clone + From<Page> + 'static>(&'a self, sub_page: impl SubPage) -> Element<'a, Message> {
|
fn view_unimplemented_sub_page<'a, Message: Clone + From<Page> + 'static>(
|
||||||
|
&'a self,
|
||||||
|
sub_page: impl SubPage,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
self.parent_page_button(sub_page),
|
self.parent_page_button(sub_page),
|
||||||
text("We haven't created that panel yet, and/or it is using a similar idea as current Pop! designs.").into(),
|
text("We haven't created that panel yet, and/or it is using a similar idea as current Pop! designs.").into(),
|
||||||
|
|
@ -273,7 +282,10 @@ impl Application for Window {
|
||||||
// Configures the demo view switcher.
|
// Configures the demo view switcher.
|
||||||
let key = window.demo.view_switcher.insert("Controls", DemoView::TabA);
|
let key = window.demo.view_switcher.insert("Controls", DemoView::TabA);
|
||||||
window.demo.view_switcher.activate(key);
|
window.demo.view_switcher.activate(key);
|
||||||
window.demo.view_switcher.insert("Segmented Button", DemoView::TabB);
|
window
|
||||||
|
.demo
|
||||||
|
.view_switcher
|
||||||
|
.insert("Segmented Button", DemoView::TabB);
|
||||||
window.demo.view_switcher.insert("Tab C", DemoView::TabC);
|
window.demo.view_switcher.insert("Tab C", DemoView::TabC);
|
||||||
|
|
||||||
// Configures the demo selection button.
|
// Configures the demo selection button.
|
||||||
|
|
@ -282,7 +294,6 @@ impl Application for Window {
|
||||||
window.demo.selection.insert("Choice B", ());
|
window.demo.selection.insert("Choice B", ());
|
||||||
window.demo.selection.insert("Choice C", ());
|
window.demo.selection.insert("Choice C", ());
|
||||||
|
|
||||||
|
|
||||||
(window, Command::none())
|
(window, Command::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,19 +329,15 @@ impl Application for Window {
|
||||||
Message::Bluetooth(message) => {
|
Message::Bluetooth(message) => {
|
||||||
self.bluetooth.update(message);
|
self.bluetooth.update(message);
|
||||||
}
|
}
|
||||||
Message::Demo(message) => {
|
Message::Demo(message) => match self.demo.update(message) {
|
||||||
match self.demo.update(message) {
|
Some(demo::Output::Debug(debug)) => self.debug = debug,
|
||||||
Some(demo::Output::Debug(debug)) => self.debug = debug,
|
Some(demo::Output::ThemeChanged(theme)) => self.theme = theme,
|
||||||
Some(demo::Output::ThemeChanged(theme)) => self.theme = theme,
|
None => (),
|
||||||
None => (),
|
},
|
||||||
}
|
Message::Desktop(message) => match self.desktop.update(message) {
|
||||||
}
|
Some(desktop::Output::Page(page)) => self.page(page),
|
||||||
Message::Desktop(message) => {
|
None => (),
|
||||||
match self.desktop.update(message) {
|
},
|
||||||
Some(desktop::Output::Page(page)) => self.page(page),
|
|
||||||
None => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
|
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
|
||||||
Message::ToggleSidebarCondensed => {
|
Message::ToggleSidebarCondensed => {
|
||||||
self.sidebar_toggled_condensed = !self.sidebar_toggled_condensed
|
self.sidebar_toggled_condensed = !self.sidebar_toggled_condensed
|
||||||
|
|
@ -343,7 +350,6 @@ impl Application for Window {
|
||||||
Message::InputChanged => {}
|
Message::InputChanged => {}
|
||||||
|
|
||||||
Message::CondensedViewToggle(_) => {}
|
Message::CondensedViewToggle(_) => {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -451,11 +457,12 @@ impl Application for Window {
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
.into(),
|
.into(),
|
||||||
Page::Networking(Some(sub_page)) => {
|
Page::Networking(Some(sub_page)) => self.view_unimplemented_sub_page(sub_page),
|
||||||
self.view_unimplemented_sub_page(sub_page)
|
|
||||||
}
|
|
||||||
Page::Bluetooth => self.bluetooth.view(self).map(Message::Bluetooth),
|
Page::Bluetooth => self.bluetooth.view(self).map(Message::Bluetooth),
|
||||||
Page::Desktop(desktop_page_opt) => self.desktop.view(self, desktop_page_opt).map(Message::Desktop),
|
Page::Desktop(desktop_page_opt) => self
|
||||||
|
.desktop
|
||||||
|
.view(self, desktop_page_opt)
|
||||||
|
.map(Message::Desktop),
|
||||||
Page::InputDevices(None) => settings::view_column(vec![
|
Page::InputDevices(None) => settings::view_column(vec![
|
||||||
self.page_title(self.page),
|
self.page_title(self.page),
|
||||||
column!(
|
column!(
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
|
use super::{Page, Window};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Element,
|
|
||||||
iced::widget::{column, text},
|
iced::widget::{column, text},
|
||||||
widget::{list_column, settings, toggler},
|
widget::{list_column, settings, toggler},
|
||||||
|
Element,
|
||||||
};
|
};
|
||||||
use super::{Page, Window};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Enable(bool)
|
Enable(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
enabled: bool
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -25,16 +25,19 @@ impl State {
|
||||||
pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.page_title(Page::Bluetooth),
|
window.page_title(Page::Bluetooth),
|
||||||
|
|
||||||
column!(
|
column!(
|
||||||
list_column()
|
list_column().add(settings::item(
|
||||||
.add(settings::item("Bluetooth", toggler(None, self.enabled, Message::Enable))),
|
"Bluetooth",
|
||||||
|
toggler(None, self.enabled, Message::Enable)
|
||||||
|
)),
|
||||||
text("Now visible as \"TODO\", just kidding")
|
text("Now visible as \"TODO\", just kidding")
|
||||||
).spacing(8).into(),
|
)
|
||||||
|
.spacing(8)
|
||||||
|
.into(),
|
||||||
settings::view_section("Devices")
|
settings::view_section("Devices")
|
||||||
.add(settings::item("No devices found", text("")))
|
.add(settings::item("No devices found", text("")))
|
||||||
.into()
|
.into(),
|
||||||
]).into()
|
])
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,22 @@ use cosmic::{
|
||||||
iced::widget::{checkbox, pick_list, progress_bar, radio, row, slider},
|
iced::widget::{checkbox, pick_list, progress_bar, radio, row, slider},
|
||||||
iced::{Alignment, Length},
|
iced::{Alignment, Length},
|
||||||
theme::{Button as ButtonTheme, Theme},
|
theme::{Button as ButtonTheme, Theme},
|
||||||
widget::{button, settings, toggler, spin_button::{SpinButtonModel, SpinMessage}},
|
widget::{
|
||||||
|
button, settings,
|
||||||
|
spin_button::{SpinButtonModel, SpinMessage},
|
||||||
|
toggler,
|
||||||
|
},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
|
|
||||||
use cosmic::widget::segmented_button::{self, cosmic::{horizontal_segmented_selection, horizontal_view_switcher, vertical_segmented_selection, vertical_view_switcher}};
|
|
||||||
use super::{Page, Window};
|
use super::{Page, Window};
|
||||||
|
use cosmic::widget::segmented_button::{
|
||||||
|
self,
|
||||||
|
cosmic::{
|
||||||
|
horizontal_segmented_selection, horizontal_view_switcher, vertical_segmented_selection,
|
||||||
|
vertical_view_switcher,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub enum DemoView {
|
pub enum DemoView {
|
||||||
TabA,
|
TabA,
|
||||||
|
|
@ -32,7 +42,7 @@ pub enum Message {
|
||||||
|
|
||||||
pub enum Output {
|
pub enum Output {
|
||||||
Debug(bool),
|
Debug(bool),
|
||||||
ThemeChanged(Theme)
|
ThemeChanged(Theme),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -166,86 +176,84 @@ impl State {
|
||||||
])
|
])
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.into(),
|
.into(),
|
||||||
Some(DemoView::TabB) => {
|
Some(DemoView::TabB) => settings::view_column(vec![
|
||||||
settings::view_column(vec![
|
cosmic::iced::widget::text("Selection")
|
||||||
cosmic::iced::widget::text("Selection")
|
.font(cosmic::font::FONT_SEMIBOLD)
|
||||||
.font(cosmic::font::FONT_SEMIBOLD)
|
|
||||||
.into(),
|
|
||||||
cosmic::iced::widget::text("Horizontal").into(),
|
|
||||||
horizontal_segmented_selection(&self.selection)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.into(),
|
|
||||||
cosmic::iced::widget::text("Horizontal With Spacing").into(),
|
|
||||||
horizontal_segmented_selection(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.into(),
|
|
||||||
cosmic::iced::widget::text("Vertical").into(),
|
|
||||||
vertical_segmented_selection(&self.selection)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.into(),
|
|
||||||
cosmic::iced::widget::text("Vertical With Spacing").into(),
|
|
||||||
cosmic::iced::widget::row(vec![
|
|
||||||
vertical_segmented_selection(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.width(Length::FillPortion(1))
|
|
||||||
.into(),
|
|
||||||
vertical_segmented_selection(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.width(Length::FillPortion(1))
|
|
||||||
.into(),
|
|
||||||
vertical_segmented_selection(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.width(Length::FillPortion(1))
|
|
||||||
.into(),
|
|
||||||
])
|
|
||||||
.spacing(12)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.into(),
|
.into(),
|
||||||
cosmic::iced::widget::text("View Switcher")
|
cosmic::iced::widget::text("Horizontal").into(),
|
||||||
.font(cosmic::font::FONT_SEMIBOLD)
|
horizontal_segmented_selection(&self.selection)
|
||||||
.into(),
|
.on_activate(Message::Selection)
|
||||||
cosmic::iced::widget::text("Horizontal").into(),
|
.into(),
|
||||||
horizontal_view_switcher(&self.selection)
|
cosmic::iced::widget::text("Horizontal With Spacing").into(),
|
||||||
.on_activate(Message::Selection)
|
horizontal_segmented_selection(&self.selection)
|
||||||
.into(),
|
.spacing(8)
|
||||||
cosmic::iced::widget::text("Horizontal With Spacing").into(),
|
.on_activate(Message::Selection)
|
||||||
horizontal_view_switcher(&self.selection)
|
.into(),
|
||||||
|
cosmic::iced::widget::text("Vertical").into(),
|
||||||
|
vertical_segmented_selection(&self.selection)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.into(),
|
||||||
|
cosmic::iced::widget::text("Vertical With Spacing").into(),
|
||||||
|
cosmic::iced::widget::row(vec![
|
||||||
|
vertical_segmented_selection(&self.selection)
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.on_activate(Message::Selection)
|
.on_activate(Message::Selection)
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
.into(),
|
.into(),
|
||||||
cosmic::iced::widget::text("Vertical").into(),
|
vertical_segmented_selection(&self.selection)
|
||||||
vertical_view_switcher(&self.selection)
|
.spacing(8)
|
||||||
.on_activate(Message::Selection)
|
.on_activate(Message::Selection)
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
|
.into(),
|
||||||
|
vertical_segmented_selection(&self.selection)
|
||||||
|
.spacing(8)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
.into(),
|
.into(),
|
||||||
cosmic::iced::widget::text("Vertical With Spacing").into(),
|
|
||||||
cosmic::iced::widget::row(vec![
|
|
||||||
vertical_view_switcher(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.width(Length::FillPortion(1))
|
|
||||||
.into(),
|
|
||||||
vertical_view_switcher(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.width(Length::FillPortion(1))
|
|
||||||
.into(),
|
|
||||||
vertical_view_switcher(&self.selection)
|
|
||||||
.spacing(8)
|
|
||||||
.on_activate(Message::Selection)
|
|
||||||
.width(Length::FillPortion(1))
|
|
||||||
.into(),
|
|
||||||
])
|
|
||||||
.spacing(12)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.into()
|
|
||||||
])
|
])
|
||||||
.padding(0)
|
.spacing(12)
|
||||||
.into()
|
.width(Length::Fill)
|
||||||
}
|
.into(),
|
||||||
|
cosmic::iced::widget::text("View Switcher")
|
||||||
|
.font(cosmic::font::FONT_SEMIBOLD)
|
||||||
|
.into(),
|
||||||
|
cosmic::iced::widget::text("Horizontal").into(),
|
||||||
|
horizontal_view_switcher(&self.selection)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.into(),
|
||||||
|
cosmic::iced::widget::text("Horizontal With Spacing").into(),
|
||||||
|
horizontal_view_switcher(&self.selection)
|
||||||
|
.spacing(8)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.into(),
|
||||||
|
cosmic::iced::widget::text("Vertical").into(),
|
||||||
|
vertical_view_switcher(&self.selection)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.into(),
|
||||||
|
cosmic::iced::widget::text("Vertical With Spacing").into(),
|
||||||
|
cosmic::iced::widget::row(vec![
|
||||||
|
vertical_view_switcher(&self.selection)
|
||||||
|
.spacing(8)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
|
.into(),
|
||||||
|
vertical_view_switcher(&self.selection)
|
||||||
|
.spacing(8)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
|
.into(),
|
||||||
|
vertical_view_switcher(&self.selection)
|
||||||
|
.spacing(8)
|
||||||
|
.on_activate(Message::Selection)
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.spacing(12)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.padding(0)
|
||||||
|
.into(),
|
||||||
Some(DemoView::TabC) => {
|
Some(DemoView::TabC) => {
|
||||||
settings::view_column(vec![settings::view_section("Tab C")
|
settings::view_column(vec![settings::view_section("Tab C")
|
||||||
.add(cosmic::iced::widget::text("Nothing here yet").width(Length::Fill))
|
.add(cosmic::iced::widget::text("Nothing here yet").width(Length::Fill))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Element,
|
|
||||||
iced::Length,
|
|
||||||
iced::widget::{column, container, horizontal_space, image, row, svg, text},
|
iced::widget::{column, container, horizontal_space, image, row, svg, text},
|
||||||
|
iced::Length,
|
||||||
theme,
|
theme,
|
||||||
widget::{list_column, settings, toggler},
|
widget::{list_column, settings, toggler},
|
||||||
|
Element,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Page, SubPage, Window};
|
use super::{Page, SubPage, Window};
|
||||||
|
|
@ -74,7 +74,9 @@ impl SubPage for DesktopPage {
|
||||||
Appearance => "Accent colors and COSMIC theming",
|
Appearance => "Accent colors and COSMIC theming",
|
||||||
DockAndTopPanel => "Customize size, positions, and more for Dock and Top Panel.",
|
DockAndTopPanel => "Customize size, positions, and more for Dock and Top Panel.",
|
||||||
Workspaces => "Set workspace number, behavior, and placement.",
|
Workspaces => "Set workspace number, behavior, and placement.",
|
||||||
Notifications => "Do Not Disturb, lockscreen notifications, and per-application settings.",
|
Notifications => {
|
||||||
|
"Do Not Disturb, lockscreen notifications, and per-application settings."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +116,11 @@ impl State {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn view<'a>(&'a self, window: &'a Window, desktop_page_opt: Option<DesktopPage>) -> Element<'a, Message> {
|
pub(super) fn view<'a>(
|
||||||
|
&'a self,
|
||||||
|
window: &'a Window,
|
||||||
|
desktop_page_opt: Option<DesktopPage>,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
match desktop_page_opt {
|
match desktop_page_opt {
|
||||||
None => settings::view_column(vec![
|
None => settings::view_column(vec![
|
||||||
window.page_title(window.page),
|
window.page_title(window.page),
|
||||||
|
|
@ -125,7 +131,9 @@ impl State {
|
||||||
window.sub_page_button(DesktopPage::DockAndTopPanel),
|
window.sub_page_button(DesktopPage::DockAndTopPanel),
|
||||||
window.sub_page_button(DesktopPage::Workspaces),
|
window.sub_page_button(DesktopPage::Workspaces),
|
||||||
window.sub_page_button(DesktopPage::Notifications),
|
window.sub_page_button(DesktopPage::Notifications),
|
||||||
).spacing(16).into()
|
)
|
||||||
|
.spacing(16)
|
||||||
|
.into(),
|
||||||
])
|
])
|
||||||
.into(),
|
.into(),
|
||||||
Some(DesktopPage::DesktopOptions) => self.view_desktop_options(window),
|
Some(DesktopPage::DesktopOptions) => self.view_desktop_options(window),
|
||||||
|
|
@ -138,27 +146,50 @@ impl State {
|
||||||
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_options<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::DesktopOptions),
|
window.parent_page_button(DesktopPage::DesktopOptions),
|
||||||
|
|
||||||
settings::view_section("Super Key Action")
|
settings::view_section("Super Key Action")
|
||||||
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
.add(settings::item("Launcher", horizontal_space(Length::Fill)))
|
||||||
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
.add(settings::item("Workspaces", horizontal_space(Length::Fill)))
|
||||||
.add(settings::item("Applications", horizontal_space(Length::Fill)))
|
.add(settings::item(
|
||||||
|
"Applications",
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Hot Corner")
|
settings::view_section("Hot Corner")
|
||||||
.add(settings::item("Enable top-left hot corner for Workspaces", toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner)))
|
.add(settings::item(
|
||||||
|
"Enable top-left hot corner for Workspaces",
|
||||||
|
toggler(None, self.top_left_hot_corner, Message::TopLeftHotCorner),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Top Panel")
|
settings::view_section("Top Panel")
|
||||||
.add(settings::item("Show Workspaces Button", toggler(None, self.show_workspaces_button, Message::ShowWorkspacesButton)))
|
.add(settings::item(
|
||||||
.add(settings::item("Show Applications Button", toggler(None, self.show_applications_button, Message::ShowApplicationsButton)))
|
"Show Workspaces Button",
|
||||||
|
toggler(
|
||||||
|
None,
|
||||||
|
self.show_workspaces_button,
|
||||||
|
Message::ShowWorkspacesButton,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
"Show Applications Button",
|
||||||
|
toggler(
|
||||||
|
None,
|
||||||
|
self.show_applications_button,
|
||||||
|
Message::ShowApplicationsButton,
|
||||||
|
),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Window Controls")
|
settings::view_section("Window Controls")
|
||||||
.add(settings::item("Show Minimize Button", toggler(None, self.show_minimize_button, Message::ShowMinimizeButton)))
|
.add(settings::item(
|
||||||
.add(settings::item("Show Maximize Button", toggler(None, self.show_maximize_button, Message::ShowMaximizeButton)))
|
"Show Minimize Button",
|
||||||
|
toggler(None, self.show_minimize_button, Message::ShowMinimizeButton),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
"Show Maximize Button",
|
||||||
|
toggler(None, self.show_maximize_button, Message::ShowMaximizeButton),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
]).into()
|
])
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_desktop_wallpaper<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_wallpaper<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
|
|
@ -186,55 +217,70 @@ impl State {
|
||||||
for chunk in image_paths.chunks(4) {
|
for chunk in image_paths.chunks(4) {
|
||||||
let mut image_row = Vec::with_capacity(chunk.len());
|
let mut image_row = Vec::with_capacity(chunk.len());
|
||||||
for image_path in chunk.iter() {
|
for image_path in chunk.iter() {
|
||||||
image_row.push(
|
image_row.push(if image_path.ends_with(".svg") {
|
||||||
if image_path.ends_with(".svg") {
|
svg(svg::Handle::from_path(image_path))
|
||||||
svg(svg::Handle::from_path(image_path)).width(Length::Units(150)).into()
|
.width(Length::Units(150))
|
||||||
} else {
|
.into()
|
||||||
image(image_path).width(Length::Units(150)).into()
|
} else {
|
||||||
}
|
image(image_path).width(Length::Units(150)).into()
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
image_column.push(row(image_row).spacing(16).into());
|
image_column.push(row(image_row).spacing(16).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::Wallpaper),
|
window.parent_page_button(DesktopPage::Wallpaper),
|
||||||
|
|
||||||
row!(
|
row!(
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
container(
|
container(
|
||||||
image(
|
image("/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png")
|
||||||
"/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png"
|
.width(Length::Units(300))
|
||||||
).width(Length::Units(300))
|
|
||||||
)
|
)
|
||||||
.padding(4)
|
.padding(4)
|
||||||
.style(theme::Container::Box),
|
.style(theme::Container::Box),
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
).into(),
|
)
|
||||||
|
.into(),
|
||||||
list_column()
|
list_column()
|
||||||
.add(settings::item("Same background on all displays", toggler(None, self.same_background, Message::SameBackground)))
|
.add(settings::item(
|
||||||
|
"Same background on all displays",
|
||||||
|
toggler(None, self.same_background, Message::SameBackground),
|
||||||
|
))
|
||||||
.add(settings::item("Background fit", text("TODO")))
|
.add(settings::item("Background fit", text("TODO")))
|
||||||
.add(settings::item("Slideshow", toggler(None, self.slideshow, Message::Slideshow)))
|
.add(settings::item(
|
||||||
|
"Slideshow",
|
||||||
|
toggler(None, self.slideshow, Message::Slideshow),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
column(image_column).spacing(16).into(),
|
column(image_column).spacing(16).into(),
|
||||||
]).into()
|
])
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
fn view_desktop_workspaces<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(DesktopPage::Wallpaper),
|
window.parent_page_button(DesktopPage::Wallpaper),
|
||||||
|
|
||||||
settings::view_section("Workspace Behavior")
|
settings::view_section("Workspace Behavior")
|
||||||
.add(settings::item("Dynamic workspaces", horizontal_space(Length::Fill)))
|
.add(settings::item(
|
||||||
.add(settings::item("Fixed Number of Workspaces", horizontal_space(Length::Fill)))
|
"Dynamic workspaces",
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
"Fixed Number of Workspaces",
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Multi-monitor Behavior")
|
settings::view_section("Multi-monitor Behavior")
|
||||||
.add(settings::item("Workspaces Span Displays", horizontal_space(Length::Fill)))
|
.add(settings::item(
|
||||||
.add(settings::item("Displays Have Separate Workspaces", horizontal_space(Length::Fill)))
|
"Workspaces Span Displays",
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
"Displays Have Separate Workspaces",
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
]).into()
|
])
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Element,
|
|
||||||
iced::Length,
|
|
||||||
iced::widget::{horizontal_space, row, text},
|
iced::widget::{horizontal_space, row, text},
|
||||||
|
iced::Length,
|
||||||
widget::{icon, list_column, settings},
|
widget::{icon, list_column, settings},
|
||||||
|
Element,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Message, Page, SubPage, Window};
|
use super::{Message, Page, SubPage, Window};
|
||||||
|
|
@ -60,17 +60,15 @@ impl State {
|
||||||
pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> {
|
||||||
settings::view_column(vec![
|
settings::view_column(vec![
|
||||||
window.parent_page_button(SystemAndAccountsPage::About),
|
window.parent_page_button(SystemAndAccountsPage::About),
|
||||||
|
|
||||||
row!(
|
row!(
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
icon("distributor-logo", 78),
|
icon("distributor-logo", 78),
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
).into(),
|
)
|
||||||
|
.into(),
|
||||||
list_column()
|
list_column()
|
||||||
.add(settings::item("Device name", text("TODO")))
|
.add(settings::item("Device name", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Hardware")
|
settings::view_section("Hardware")
|
||||||
.add(settings::item("Hardware model", text("TODO")))
|
.add(settings::item("Hardware model", text("TODO")))
|
||||||
.add(settings::item("Memory", text("TODO")))
|
.add(settings::item("Memory", text("TODO")))
|
||||||
|
|
@ -78,17 +76,19 @@ impl State {
|
||||||
.add(settings::item("Graphics", text("TODO")))
|
.add(settings::item("Graphics", text("TODO")))
|
||||||
.add(settings::item("Disk Capacity", text("TODO")))
|
.add(settings::item("Disk Capacity", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Operating System")
|
settings::view_section("Operating System")
|
||||||
.add(settings::item("Operating system", text("TODO")))
|
.add(settings::item("Operating system", text("TODO")))
|
||||||
.add(settings::item("Operating system architecture", text("TODO")))
|
.add(settings::item(
|
||||||
|
"Operating system architecture",
|
||||||
|
text("TODO"),
|
||||||
|
))
|
||||||
.add(settings::item("Desktop environment", text("TODO")))
|
.add(settings::item("Desktop environment", text("TODO")))
|
||||||
.add(settings::item("Windowing system", text("TODO")))
|
.add(settings::item("Windowing system", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
|
|
||||||
settings::view_section("Related settings")
|
settings::view_section("Related settings")
|
||||||
.add(settings::item("Get support", text("TODO")))
|
.add(settings::item("Get support", text("TODO")))
|
||||||
.into(),
|
.into(),
|
||||||
]).into()
|
])
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,6 @@ pub fn settings<Flags: Default>() -> iced::Settings<Flags> {
|
||||||
iced::Font::External { bytes, .. } => Some(bytes),
|
iced::Font::External { bytes, .. } => Some(bytes),
|
||||||
},
|
},
|
||||||
default_text_size: 18,
|
default_text_size: 18,
|
||||||
.. iced::Settings::default()
|
..iced::Settings::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ pub enum Button {
|
||||||
Transparent,
|
Transparent,
|
||||||
Custom {
|
Custom {
|
||||||
active: fn(&Theme) -> button::Appearance,
|
active: fn(&Theme) -> button::Appearance,
|
||||||
hover: fn(&Theme) -> button::Appearance
|
hover: fn(&Theme) -> button::Appearance,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +161,7 @@ impl Button {
|
||||||
Button::LinkActive => &cosmic.secondary.component,
|
Button::LinkActive => &cosmic.secondary.component,
|
||||||
Button::Transparent => &TRANSPARENT_COMPONENT,
|
Button::Transparent => &TRANSPARENT_COMPONENT,
|
||||||
Button::Deactivated => &cosmic.secondary.component,
|
Button::Deactivated => &cosmic.secondary.component,
|
||||||
Button::Custom { .. } => &TRANSPARENT_COMPONENT
|
Button::Custom { .. } => &TRANSPARENT_COMPONENT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +170,7 @@ impl button::StyleSheet for Theme {
|
||||||
type Style = Button;
|
type Style = Button;
|
||||||
|
|
||||||
fn active(&self, style: &Self::Style) -> button::Appearance {
|
fn active(&self, style: &Self::Style) -> button::Appearance {
|
||||||
if let Button::Custom {active, ..} = style {
|
if let Button::Custom { active, .. } = style {
|
||||||
return active(self);
|
return active(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,7 +196,7 @@ impl button::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hovered(&self, style: &Self::Style) -> button::Appearance {
|
fn hovered(&self, style: &Self::Style) -> button::Appearance {
|
||||||
if let Button::Custom {hover, ..} = style {
|
if let Button::Custom { hover, .. } = style {
|
||||||
return hover(self);
|
return hover(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ impl button::StyleSheet for Theme {
|
||||||
background: match style {
|
background: match style {
|
||||||
Button::Link => None,
|
Button::Link => None,
|
||||||
Button::LinkActive => Some(Background::Color(cosmic.divider.into())),
|
Button::LinkActive => Some(Background::Color(cosmic.divider.into())),
|
||||||
_ => Some(Background::Color(cosmic.hover.into()))
|
_ => Some(Background::Color(cosmic.hover.into())),
|
||||||
},
|
},
|
||||||
..active
|
..active
|
||||||
}
|
}
|
||||||
|
|
@ -234,11 +234,7 @@ impl Default for Checkbox {
|
||||||
impl checkbox::StyleSheet for Theme {
|
impl checkbox::StyleSheet for Theme {
|
||||||
type Style = Checkbox;
|
type Style = Checkbox;
|
||||||
|
|
||||||
fn active(
|
fn active(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance {
|
||||||
&self,
|
|
||||||
style: &Self::Style,
|
|
||||||
is_checked: bool,
|
|
||||||
) -> checkbox::Appearance {
|
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
match style {
|
match style {
|
||||||
|
|
@ -269,11 +265,7 @@ impl checkbox::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hovered(
|
fn hovered(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance {
|
||||||
&self,
|
|
||||||
style: &Self::Style,
|
|
||||||
is_checked: bool,
|
|
||||||
) -> checkbox::Appearance {
|
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
match style {
|
match style {
|
||||||
|
|
@ -421,9 +413,7 @@ impl slider::StyleSheet for Theme {
|
||||||
|
|
||||||
fn hovered(&self, style: &Self::Style) -> slider::Appearance {
|
fn hovered(&self, style: &Self::Style) -> slider::Appearance {
|
||||||
let mut style = self.active(style);
|
let mut style = self.active(style);
|
||||||
style.handle.shape = slider::HandleShape::Circle {
|
style.handle.shape = slider::HandleShape::Circle { radius: 16.0 };
|
||||||
radius: 16.0
|
|
||||||
};
|
|
||||||
style.handle.border_width = 6.0;
|
style.handle.border_width = 6.0;
|
||||||
style.handle.border_color = match self {
|
style.handle.border_color = match self {
|
||||||
Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.1),
|
Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.1),
|
||||||
|
|
@ -529,11 +519,7 @@ impl radio::StyleSheet for Theme {
|
||||||
impl toggler::StyleSheet for Theme {
|
impl toggler::StyleSheet for Theme {
|
||||||
type Style = ();
|
type Style = ();
|
||||||
|
|
||||||
fn active(
|
fn active(&self, _style: &Self::Style, is_active: bool) -> toggler::Appearance {
|
||||||
&self,
|
|
||||||
_style: &Self::Style,
|
|
||||||
is_active: bool,
|
|
||||||
) -> toggler::Appearance {
|
|
||||||
let palette = self.palette();
|
let palette = self.palette();
|
||||||
|
|
||||||
toggler::Appearance {
|
toggler::Appearance {
|
||||||
|
|
@ -556,11 +542,7 @@ impl toggler::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hovered(
|
fn hovered(&self, style: &Self::Style, is_active: bool) -> toggler::Appearance {
|
||||||
&self,
|
|
||||||
style: &Self::Style,
|
|
||||||
is_active: bool,
|
|
||||||
) -> toggler::Appearance {
|
|
||||||
//TODO: grab colors from palette
|
//TODO: grab colors from palette
|
||||||
match self {
|
match self {
|
||||||
Theme::Dark => toggler::Appearance {
|
Theme::Dark => toggler::Appearance {
|
||||||
|
|
@ -578,7 +560,7 @@ impl toggler::StyleSheet for Theme {
|
||||||
Color::from_rgb8(0x54, 0x54, 0x54)
|
Color::from_rgb8(0x54, 0x54, 0x54)
|
||||||
},
|
},
|
||||||
..self.active(style, is_active)
|
..self.active(style, is_active)
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use iced::Size;
|
|
||||||
use iced::widget::Container;
|
use iced::widget::Container;
|
||||||
|
use iced::Size;
|
||||||
use iced_native::alignment;
|
use iced_native::alignment;
|
||||||
use iced_native::event::{self, Event};
|
use iced_native::event::{self, Event};
|
||||||
use iced_native::layout;
|
use iced_native::layout;
|
||||||
|
|
@ -13,7 +13,7 @@ pub use iced_style::container::{Appearance, StyleSheet};
|
||||||
|
|
||||||
pub fn aspect_ratio_container<'a, Message: 'static, T>(
|
pub fn aspect_ratio_container<'a, Message: 'static, T>(
|
||||||
content: T,
|
content: T,
|
||||||
ratio: f32
|
ratio: f32,
|
||||||
) -> AspectRatio<'a, Message, crate::Renderer>
|
) -> AspectRatio<'a, Message, crate::Renderer>
|
||||||
where
|
where
|
||||||
T: Into<Element<'a, Message, crate::Renderer>>,
|
T: Into<Element<'a, Message, crate::Renderer>>,
|
||||||
|
|
@ -40,13 +40,16 @@ where
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
fn constrain_limits(&self, size: Size) -> Size {
|
fn constrain_limits(&self, size: Size) -> Size {
|
||||||
let Size { mut width, mut height } = size;
|
let Size {
|
||||||
|
mut width,
|
||||||
|
mut height,
|
||||||
|
} = size;
|
||||||
if size.width / size.height > self.ratio {
|
if size.width / size.height > self.ratio {
|
||||||
width = self.ratio * height;
|
width = self.ratio * height;
|
||||||
} else {
|
} else {
|
||||||
height = width / self.ratio;
|
height = width / self.ratio;
|
||||||
}
|
}
|
||||||
Size { width, height }
|
Size { width, height }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,8 +140,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for AspectRatio<'a, Message, Renderer>
|
||||||
for AspectRatio<'a, Message, Renderer>
|
|
||||||
where
|
where
|
||||||
Renderer: iced_native::Renderer,
|
Renderer: iced_native::Renderer,
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
|
|
@ -160,7 +162,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||||
let custom_limits = layout::Limits::new(self.constrain_limits(limits.min()), self.constrain_limits(limits.max()));
|
let custom_limits = layout::Limits::new(
|
||||||
|
self.constrain_limits(limits.min()),
|
||||||
|
self.constrain_limits(limits.max()),
|
||||||
|
);
|
||||||
self.container.layout(renderer, &custom_limits)
|
self.container.layout(renderer, &custom_limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,9 +244,7 @@ where
|
||||||
Renderer: 'a + iced_native::Renderer,
|
Renderer: 'a + iced_native::Renderer,
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(column: AspectRatio<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
||||||
column: AspectRatio<'a, Message, Renderer>,
|
|
||||||
) -> Element<'a, Message, Renderer> {
|
|
||||||
Element::new(column)
|
Element::new(column)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,10 @@ use iced::widget;
|
||||||
/// A button widget with COSMIC styling
|
/// A button widget with COSMIC styling
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn button<Message>(style: theme::Button) -> Button<Message> {
|
pub const fn button<Message>(style: theme::Button) -> Button<Message> {
|
||||||
Button { style, message: None }
|
Button {
|
||||||
|
style,
|
||||||
|
message: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A button widget with COSMIC styling
|
/// A button widget with COSMIC styling
|
||||||
|
|
@ -25,7 +28,12 @@ impl<Message: 'static> Button<Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A button with an icon.
|
/// A button with an icon.
|
||||||
pub fn icon(self, style: theme::Svg, icon: &str, size: u16) -> widget::Button<Message, Renderer> {
|
pub fn icon(
|
||||||
|
self,
|
||||||
|
style: theme::Svg,
|
||||||
|
icon: &str,
|
||||||
|
size: u16,
|
||||||
|
) -> widget::Button<Message, Renderer> {
|
||||||
self.custom(vec![super::icon(icon, size).style(style).into()])
|
self.custom(vec![super::icon(icon, size).style(style).into()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,4 +55,3 @@ impl<Message: 'static> Button<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::{theme, Element};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::{self, widget, Length};
|
use iced::{self, widget, Length};
|
||||||
use crate::{theme, Element};
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
||||||
|
|
@ -36,7 +36,7 @@ pub struct HeaderBar<'a, Message> {
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
center: Option<Element<'a, Message>>,
|
center: Option<Element<'a, Message>>,
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
end: Option<Element<'a, Message>>
|
end: Option<Element<'a, Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
|
|
@ -45,11 +45,17 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
let mut packed: Vec<Element<Message>> = Vec::with_capacity(4);
|
let mut packed: Vec<Element<Message>> = Vec::with_capacity(4);
|
||||||
|
|
||||||
if let Some(start) = self.start.take() {
|
if let Some(start) = self.start.take() {
|
||||||
packed.push(widget::container(start).align_x(iced::alignment::Horizontal::Left).into());
|
packed.push(
|
||||||
|
widget::container(start)
|
||||||
|
.align_x(iced::alignment::Horizontal::Left)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
packed.push(if let Some(center) = self.center.take() {
|
packed.push(if let Some(center) = self.center.take() {
|
||||||
widget::container(center).align_x(iced::alignment::Horizontal::Center).into()
|
widget::container(center)
|
||||||
|
.align_x(iced::alignment::Horizontal::Center)
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
self.title_widget()
|
self.title_widget()
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,16 @@
|
||||||
|
|
||||||
//! Lazily-generated SVG icon widget for Iced.
|
//! Lazily-generated SVG icon widget for Iced.
|
||||||
|
|
||||||
|
use crate::{Element, Renderer};
|
||||||
|
use derive_setters::Setters;
|
||||||
use iced::{
|
use iced::{
|
||||||
widget::{svg, Image},
|
widget::{svg, Image},
|
||||||
Length, ContentFit,
|
ContentFit, Length,
|
||||||
};
|
};
|
||||||
use std::{borrow::Cow, collections::hash_map::DefaultHasher, ffi::OsStr, hash::Hasher, path::Path};
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use derive_setters::Setters;
|
use std::{
|
||||||
use crate::{Element, Renderer};
|
borrow::Cow, collections::hash_map::DefaultHasher, ffi::OsStr, hash::Hasher, path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub enum IconSource<'a> {
|
pub enum IconSource<'a> {
|
||||||
|
|
@ -65,7 +67,6 @@ pub struct Icon<'a> {
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
height: Option<Length>,
|
height: Option<Length>,
|
||||||
force_svg: bool,
|
force_svg: bool,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A lazily-generated icon.
|
/// A lazily-generated icon.
|
||||||
|
|
@ -79,7 +80,7 @@ pub fn icon<'a>(name: impl Into<IconSource<'a>>, size: u16) -> Icon<'a> {
|
||||||
style: crate::theme::Svg::default(),
|
style: crate::theme::Svg::default(),
|
||||||
theme: Cow::Borrowed("Pop"),
|
theme: Cow::Borrowed("Pop"),
|
||||||
width: None,
|
width: None,
|
||||||
force_svg: false
|
force_svg: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,8 +89,8 @@ impl<'a> Icon<'a> {
|
||||||
fn into_element<Message: 'static>(self) -> Element<'a, Message> {
|
fn into_element<Message: 'static>(self) -> Element<'a, Message> {
|
||||||
if let IconSource::Embedded(mut image) = self.name {
|
if let IconSource::Embedded(mut image) = self.name {
|
||||||
image = image
|
image = image
|
||||||
.width(self.width.unwrap_or(Length::Units(self.size)))
|
.width(self.width.unwrap_or(Length::Units(self.size)))
|
||||||
.height(self.height.unwrap_or(Length::Units(self.size)));
|
.height(self.height.unwrap_or(Length::Units(self.size)));
|
||||||
if let Some(content_fit) = self.content_fit {
|
if let Some(content_fit) = self.content_fit {
|
||||||
image = image.content_fit(content_fit);
|
image = image.content_fit(content_fit);
|
||||||
}
|
}
|
||||||
|
|
@ -115,19 +116,23 @@ impl<'a> Icon<'a> {
|
||||||
.find()
|
.find()
|
||||||
} else {
|
} else {
|
||||||
icon
|
icon
|
||||||
}.map(Cow::from)
|
}
|
||||||
},
|
.map(Cow::from)
|
||||||
|
}
|
||||||
IconSource::Embedded(_) => unimplemented!(),
|
IconSource::Embedded(_) => unimplemented!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_svg = self.force_svg || icon.as_ref().map_or(true, |path| path.extension() == Some(OsStr::new("svg")));
|
let is_svg = self.force_svg
|
||||||
|
|| icon
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |path| path.extension() == Some(OsStr::new("svg")));
|
||||||
|
|
||||||
if is_svg {
|
if is_svg {
|
||||||
let handle = if let Some(path) = icon {
|
let handle = if let Some(path) = icon {
|
||||||
svg::Handle::from_path(path)
|
svg::Handle::from_path(path)
|
||||||
} else {
|
} else {
|
||||||
eprintln!("icon '{:?}' size {} not found", &self.name, self.size);
|
eprintln!("icon '{:?}' size {} not found", &self.name, self.size);
|
||||||
svg::Handle::from_memory(Vec::new())
|
svg::Handle::from_memory(Vec::new())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut widget = svg::Svg::<Renderer>::new(handle)
|
let mut widget = svg::Svg::<Renderer>::new(handle)
|
||||||
|
|
@ -150,7 +155,8 @@ impl<'a> Icon<'a> {
|
||||||
}
|
}
|
||||||
image.into()
|
image.into()
|
||||||
}
|
}
|
||||||
}).into()
|
})
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use apply::Apply;
|
|
||||||
use crate::{Element, theme};
|
|
||||||
use crate::widget::horizontal_rule;
|
use crate::widget::horizontal_rule;
|
||||||
|
use crate::{theme, Element};
|
||||||
|
use apply::Apply;
|
||||||
use iced::{Background, Color};
|
use iced::{Background, Color};
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -17,7 +17,9 @@ pub struct ListColumn<'a, Message> {
|
||||||
|
|
||||||
impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
|
impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { children: Vec::with_capacity(4) }
|
Self {
|
||||||
|
children: Vec::with_capacity(4),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,5 @@
|
||||||
pub mod column;
|
pub mod column;
|
||||||
// mod item;
|
// mod item;
|
||||||
|
|
||||||
pub use self::column::{ListColumn, list_column};
|
pub use self::column::{list_column, ListColumn};
|
||||||
// pub use self::item::{ListItem, list_item};
|
// pub use self::item::{ListItem, list_item};
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@ mod button;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
|
|
||||||
mod header_bar;
|
mod header_bar;
|
||||||
pub use header_bar::{HeaderBar, header_bar};
|
pub use header_bar::{header_bar, HeaderBar};
|
||||||
|
|
||||||
mod icon;
|
mod icon;
|
||||||
pub use self::icon::{Icon, icon, IconSource};
|
pub use self::icon::{icon, Icon, IconSource};
|
||||||
|
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub use self::list::*;
|
pub use self::list::*;
|
||||||
|
|
||||||
pub mod nav_button;
|
pub mod nav_button;
|
||||||
pub use self::nav_button::{NavButton, nav_button};
|
pub use self::nav_button::{nav_button, NavButton};
|
||||||
|
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub use navigation::*;
|
pub use navigation::*;
|
||||||
|
|
@ -24,10 +24,8 @@ pub use toggler::toggler;
|
||||||
|
|
||||||
pub mod segmented_button;
|
pub mod segmented_button;
|
||||||
pub use segmented_button::{
|
pub use segmented_button::{
|
||||||
HorizontalSegmentedButton,
|
horizontal_segmented_button, vertical_segmented_button, HorizontalSegmentedButton,
|
||||||
VerticalSegmentedButton,
|
VerticalSegmentedButton,
|
||||||
horizontal_segmented_button,
|
|
||||||
vertical_segmented_button
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
@ -39,7 +37,7 @@ pub mod separator;
|
||||||
pub use separator::{horizontal_rule, vertical_rule};
|
pub use separator::{horizontal_rule, vertical_rule};
|
||||||
|
|
||||||
pub mod spin_button;
|
pub mod spin_button;
|
||||||
pub use spin_button::{SpinButton, spin_button};
|
pub use spin_button::{spin_button, SpinButton};
|
||||||
|
|
||||||
pub mod rectangle_tracker;
|
pub mod rectangle_tracker;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::{theme, Element};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::{alignment::Vertical, Length};
|
use iced::{alignment::Vertical, Length};
|
||||||
use crate::{Element, theme};
|
|
||||||
|
|
||||||
#[derive(Setters)]
|
#[derive(Setters)]
|
||||||
pub struct NavButton<'a, Message> {
|
pub struct NavButton<'a, Message> {
|
||||||
|
|
@ -54,7 +54,8 @@ impl<'a, Message: 'static + Clone> From<NavButton<'a, Message>> for Element<'a,
|
||||||
widget = widget.on_press(message);
|
widget = widget.on_press(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.apply(iced::widget::container)
|
widget
|
||||||
|
.apply(iced::widget::container)
|
||||||
.center_y()
|
.center_y()
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -174,35 +174,43 @@ impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message> {
|
||||||
|
|
||||||
let nav_bar: Element<Self::Event, Renderer> =
|
let nav_bar: Element<Self::Event, Renderer> =
|
||||||
container(if self.condensed && state.selected_page.is_some() {
|
container(if self.condensed && state.selected_page.is_some() {
|
||||||
row![container(scrollable(column(pages)
|
row![container(scrollable(
|
||||||
|
column(pages)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.max_width(200)
|
.max_width(200)
|
||||||
.width(Length::Units(200))
|
.width(Length::Units(200))
|
||||||
.height(Length::Shrink)))
|
.height(Length::Shrink)
|
||||||
.height(Length::Fill)
|
))
|
||||||
.style(theme::Container::Custom(nav_bar_pages_style))]
|
.height(Length::Fill)
|
||||||
|
.style(theme::Container::Custom(nav_bar_pages_style))]
|
||||||
} else if !state.section_active || self.condensed && state.selected_page.is_none() {
|
} else if !state.section_active || self.condensed && state.selected_page.is_none() {
|
||||||
row![scrollable(column(sections)
|
row![scrollable(
|
||||||
.spacing(10)
|
column(sections)
|
||||||
.padding(10)
|
|
||||||
.max_width(100)
|
|
||||||
.align_items(Alignment::Center)
|
|
||||||
.height(Length::Shrink))]
|
|
||||||
} else {
|
|
||||||
row![
|
|
||||||
scrollable(column(sections)
|
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.max_width(100)
|
.max_width(100)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.height(Length::Shrink)),
|
.height(Length::Shrink)
|
||||||
container(scrollable(column(pages)
|
)]
|
||||||
.spacing(10)
|
} else {
|
||||||
.padding(10)
|
row![
|
||||||
.max_width(200)
|
scrollable(
|
||||||
.width(Length::Units(200))
|
column(sections)
|
||||||
.height(Length::Shrink)))
|
.spacing(10)
|
||||||
|
.padding(10)
|
||||||
|
.max_width(100)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.height(Length::Shrink)
|
||||||
|
),
|
||||||
|
container(scrollable(
|
||||||
|
column(pages)
|
||||||
|
.spacing(10)
|
||||||
|
.padding(10)
|
||||||
|
.max_width(200)
|
||||||
|
.width(Length::Units(200))
|
||||||
|
.height(Length::Shrink)
|
||||||
|
))
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.style(theme::Container::Custom(nav_bar_pages_style)),
|
.style(theme::Container::Custom(nav_bar_pages_style)),
|
||||||
]
|
]
|
||||||
|
|
@ -217,9 +225,7 @@ impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> From<NavBar<'a, Message>>
|
impl<'a, Message: 'static> From<NavBar<'a, Message>> for Element<'a, Message, Renderer> {
|
||||||
for Element<'a, Message, Renderer>
|
|
||||||
{
|
|
||||||
fn from(nav_bar: NavBar<'a, Message>) -> Self {
|
fn from(nav_bar: NavBar<'a, Message>) -> Self {
|
||||||
iced_lazy::component(nav_bar)
|
iced_lazy::component(nav_bar)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use iced::{
|
||||||
},
|
},
|
||||||
subscription, Rectangle,
|
subscription, Rectangle,
|
||||||
};
|
};
|
||||||
use std::{fmt::Debug, hash::Hash, collections::HashMap};
|
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
||||||
|
|
||||||
use super::RectangleTracker;
|
use super::RectangleTracker;
|
||||||
|
|
||||||
|
|
@ -38,11 +38,14 @@ async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
State::Waiting(mut rx, mut map) => match rx.next().await {
|
State::Waiting(mut rx, mut map) => match rx.next().await {
|
||||||
Some(u) =>
|
Some(u) => {
|
||||||
{
|
|
||||||
if let Some(prev) = map.get(&u.0) {
|
if let Some(prev) = map.get(&u.0) {
|
||||||
let new = u.1;
|
let new = u.1;
|
||||||
if prev.width != new.width || prev.height != new.height || prev.x != new.x || prev.y != new.y {
|
if prev.width != new.width
|
||||||
|
|| prev.height != new.height
|
||||||
|
|| prev.x != new.x
|
||||||
|
|| prev.y != new.y
|
||||||
|
{
|
||||||
map.insert(u.0, new);
|
map.insert(u.0, new);
|
||||||
return (
|
return (
|
||||||
Some((id, RectangleUpdate::Rectangle(u))),
|
Some((id, RectangleUpdate::Rectangle(u))),
|
||||||
|
|
@ -57,8 +60,7 @@ async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(None, State::Waiting(rx, map))
|
(None, State::Waiting(rx, map))
|
||||||
|
}
|
||||||
},
|
|
||||||
None => (None, State::Finished),
|
None => (None, State::Finished),
|
||||||
},
|
},
|
||||||
State::Finished => iced::futures::future::pending().await,
|
State::Finished => iced::futures::future::pending().await,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
use crate::{Element, Renderer};
|
use crate::{Element, Renderer};
|
||||||
use iced::widget;
|
use iced::widget;
|
||||||
|
|
||||||
pub fn scrollable<'a, Message>(element: impl Into<Element<'a, Message>>) -> widget::Scrollable<'a, Message, Renderer> {
|
pub fn scrollable<'a, Message>(
|
||||||
|
element: impl Into<Element<'a, Message>>,
|
||||||
|
) -> widget::Scrollable<'a, Message, Renderer> {
|
||||||
widget::scrollable(element)
|
widget::scrollable(element)
|
||||||
.scrollbar_width(8)
|
.scrollbar_width(8)
|
||||||
.scroller_width(8)
|
.scroller_width(8)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,5 @@ use iced::widget::{column, Column};
|
||||||
/// A column with a predefined style for creating a settings panel
|
/// A column with a predefined style for creating a settings panel
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn view_column<Message: 'static>(children: Vec<Element<Message>>) -> Column<Message, Renderer> {
|
pub fn view_column<Message: 'static>(children: Vec<Element<Message>>) -> Column<Message, Renderer> {
|
||||||
column(children)
|
column(children).spacing(24).padding([0, 24]).max_width(678)
|
||||||
.spacing(24)
|
|
||||||
.padding([0, 24])
|
|
||||||
.max_width(678)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue