libcosmic updates

This commit is contained in:
Ashley Wulber 2024-10-16 20:36:46 -04:00 committed by Ashley Wulber
parent 9c62f19e4b
commit 0491c4baaa
91 changed files with 3550 additions and 2300 deletions

View file

@ -8,15 +8,22 @@ rust-version = "1.80"
name = "cosmic" name = "cosmic"
[features] [features]
default = ["clipboard"]
# Accessibility support # Accessibility support
a11y = ["iced/a11y", "iced_accessibility"] a11y = ["iced/a11y", "iced_accessibility"]
# Builds support for animated images # Builds support for animated images
animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"] animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"]
# XXX Use "a11y"; which is causing a panic currently # XXX autosize should not be used on winit windows unless dialogs
applet = ["wayland", "tokio", "cosmic-panel-config", "ron"] autosize = []
applet = [
"a11y",
"autosize",
"wayland",
"tokio",
"cosmic-panel-config",
"ron",
"multi-window",
]
applet-token = [] applet-token = []
clipboard = ["iced_sctk?/clipboard"]
# Use the cosmic-settings-daemon for config handling # Use the cosmic-settings-daemon for config handling
dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"] dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"]
# Debug features # Debug features
@ -61,7 +68,7 @@ wayland = [
"ashpd?/wayland", "ashpd?/wayland",
"iced_runtime/wayland", "iced_runtime/wayland",
"iced/wayland", "iced/wayland",
"iced_sctk", "iced_winit/wayland",
"cctk", "cctk",
] ]
# multi-window support # multi-window support
@ -120,7 +127,7 @@ path = "cosmic-theme"
[dependencies.iced] [dependencies.iced]
path = "./iced" path = "./iced"
default-features = false default-features = false
features = ["advanced", "image", "lazy", "svg", "web-colors"] features = ["advanced", "image", "lazy", "svg", "web-colors", "tiny-skia"]
[dependencies.iced_runtime] [dependencies.iced_runtime]
path = "./iced/runtime" path = "./iced/runtime"
@ -146,13 +153,6 @@ optional = true
[dependencies.iced_tiny_skia] [dependencies.iced_tiny_skia]
path = "./iced/tiny_skia" path = "./iced/tiny_skia"
[dependencies.iced_style]
path = "./iced/style"
[dependencies.iced_sctk]
path = "./iced/sctk"
optional = true
[dependencies.iced_winit] [dependencies.iced_winit]
path = "./iced/winit" path = "./iced/winit"
optional = true optional = true
@ -189,3 +189,9 @@ dirs = "5.0.1"
[patch."https://github.com/pop-os/libcosmic"] [patch."https://github.com/pop-os/libcosmic"]
libcosmic = { path = "./" } libcosmic = { path = "./" }
# FIXME update winit deps where necessary to use this
# [patch.crates-io]
[patch."https://github.com/pop-os/winit.git"]
# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" }
# winit = { path = "../../winit" }

View file

@ -3,7 +3,10 @@ use std::ops::Deref;
use crate::{CosmicConfigEntry, Update}; use crate::{CosmicConfigEntry, Update};
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy}; use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
use futures_util::SinkExt; use futures_util::SinkExt;
use iced_futures::futures::{self, future::pending, StreamExt}; use iced_futures::{
futures::{self, future::pending, Stream, StreamExt},
stream, Subscription,
};
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> { pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
let conn = zbus::Connection::session().await?; let conn = zbus::Connection::session().await?;
@ -58,13 +61,20 @@ pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'stat
config_id: &'static str, config_id: &'static str,
is_state: bool, is_state: bool,
) -> iced_futures::Subscription<Update<T>> { ) -> iced_futures::Subscription<Update<T>> {
let id = std::any::TypeId::of::<T>();
Subscription::run_with_id(id, watcher_stream(settings_daemon, config_id, is_state))
}
fn watcher_stream<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
settings_daemon: CosmicSettingsDaemonProxy<'static>,
config_id: &'static str,
is_state: bool,
) -> impl Stream<Item = Update<T>> {
enum Change { enum Change {
Changes(Changed), Changes(Changed),
OwnerChanged(bool), OwnerChanged(bool),
} }
stream::channel(5, move |mut tx| async move {
let id = std::any::TypeId::of::<T>();
iced_futures::subscription::channel((is_state, config_id, id), 5, move |mut tx| async move {
let version = T::VERSION; let version = T::VERSION;
let Ok(cosmic_config) = (if is_state { let Ok(cosmic_config) = (if is_state {

View file

@ -384,6 +384,7 @@ where
) -> (Vec<crate::Error>, Vec<&'static str>); ) -> (Vec<crate::Error>, Vec<&'static str>);
} }
#[derive(Debug)]
pub struct Update<T> { pub struct Update<T> {
pub errors: Vec<crate::Error>, pub errors: Vec<crate::Error>,
pub keys: Vec<&'static str>, pub keys: Vec<&'static str>,

View file

@ -1,5 +1,5 @@
use iced_futures::futures::SinkExt; use iced_futures::futures::{SinkExt, Stream};
use iced_futures::{futures::channel::mpsc, subscription}; use iced_futures::{futures::channel::mpsc, stream};
use notify::RecommendedWatcher; use notify::RecommendedWatcher;
use std::{borrow::Cow, hash::Hash}; use std::{borrow::Cow, hash::Hash};
@ -24,17 +24,7 @@ pub fn config_subscription<
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
subscription::channel(id, 100, move |mut output| { iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false))
let config_id = config_id.clone();
async move {
let config_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, false);
loop {
state = start_listening(state, &mut output, id).await;
}
}
})
} }
pub fn config_state_subscription< pub fn config_state_subscription<
@ -45,26 +35,30 @@ pub fn config_state_subscription<
config_id: Cow<'static, str>, config_id: Cow<'static, str>,
config_version: u64, config_version: u64,
) -> iced_futures::Subscription<crate::Update<T>> { ) -> iced_futures::Subscription<crate::Update<T>> {
subscription::channel(id, 100, move |mut output| { iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, true))
}
fn watcher_stream<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
config_id: Cow<'static, str>,
config_version: u64,
is_state: bool,
) -> impl Stream<Item = crate::Update<T>> {
stream::channel(100, move |mut output| {
let config_id = config_id.clone(); let config_id = config_id.clone();
async move { async move {
let config_id = config_id.clone(); let config_id = config_id.clone();
let mut state = ConfigState::Init(config_id, config_version, true); let mut state = ConfigState::Init(config_id, config_version, is_state);
loop { loop {
state = start_listening(state, &mut output, id).await; state = start_listening::<T>(state, &mut output).await;
} }
} }
}) })
} }
async fn start_listening< async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
I: Copy,
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
>(
state: ConfigState<T>, state: ConfigState<T>,
output: &mut mpsc::Sender<crate::Update<T>>, output: &mut mpsc::Sender<crate::Update<T>>,
id: I,
) -> ConfigState<T> { ) -> ConfigState<T> {
use iced_futures::futures::{future::pending, StreamExt}; use iced_futures::futures::{future::pending, StreamExt};

View file

@ -9,8 +9,20 @@ edition = "2021"
once_cell = "1" once_cell = "1"
rust-embed = "8.0.0" rust-embed = "8.0.0"
tracing = "0.1" tracing = "0.1"
env_logger = "0.10.0"
log = "0.4.17"
[dependencies.libcosmic] [dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic" git = "https://github.com/pop-os/libcosmic"
default-features = false default-features = false
features = ["applet", "tokio", "wayland"] features = [
"applet",
"applet-token",
"multi-window",
"tokio",
"wayland",
"winit",
"desktop",
"dbus-config",
"image",
]

View file

@ -3,5 +3,10 @@ use crate::window::Window;
mod window; mod window;
fn main() -> cosmic::iced::Result { fn main() -> cosmic::iced::Result {
cosmic::applet::run::<Window>(true, ()) let env = env_logger::Env::default()
.filter_or("MY_LOG_LEVEL", "warn")
.write_style_or("MY_LOG_STYLE", "always");
env_logger::init_from_env(env);
cosmic::applet::run::<Window>(())
} }

View file

@ -1,9 +1,10 @@
use cosmic::app::Core; use cosmic::app::Core;
use cosmic::iced::wayland::popup::{destroy_popup, get_popup}; use cosmic::iced::application;
use cosmic::iced::platform_specific::shell::commands::popup::{destroy_popup, get_popup};
use cosmic::iced::window::Id; use cosmic::iced::window::Id;
use cosmic::iced::{Command, Limits}; use cosmic::iced::{Length, Limits, Task};
use cosmic::iced_runtime::core::window; use cosmic::iced_runtime::core::window;
use cosmic::iced_style::application; use cosmic::theme::iced;
use cosmic::widget::{list_column, settings, toggler}; use cosmic::widget::{list_column, settings, toggler};
use cosmic::{Element, Theme}; use cosmic::{Element, Theme};
@ -37,22 +38,19 @@ impl cosmic::Application for Window {
&mut self.core &mut self.core
} }
fn init( fn init(core: Core, _flags: Self::Flags) -> (Self, Task<cosmic::app::Message<Self::Message>>) {
core: Core,
_flags: Self::Flags,
) -> (Self, Command<cosmic::app::Message<Self::Message>>) {
let window = Window { let window = Window {
core, core,
..Default::default() ..Default::default()
}; };
(window, Command::none()) (window, Task::none())
} }
fn on_close_requested(&self, id: window::Id) -> Option<Message> { fn on_close_requested(&self, id: window::Id) -> Option<Message> {
Some(Message::PopupClosed(id)) Some(Message::PopupClosed(id))
} }
fn update(&mut self, message: Self::Message) -> Command<cosmic::app::Message<Self::Message>> { fn update(&mut self, message: Self::Message) -> Task<cosmic::app::Message<Self::Message>> {
match message { match message {
Message::TogglePopup => { Message::TogglePopup => {
return if let Some(p) = self.popup.take() { return if let Some(p) = self.popup.take() {
@ -60,17 +58,23 @@ impl cosmic::Application for Window {
} else { } else {
let new_id = Id::unique(); let new_id = Id::unique();
self.popup.replace(new_id); self.popup.replace(new_id);
let mut popup_settings = let mut popup_settings = self.core.applet.get_popup_settings(
self.core self.core.main_window_id().unwrap(),
.applet new_id,
.get_popup_settings(Id::MAIN, new_id, None, None, None); None,
None,
None,
);
popup_settings.positioner.size_limits = Limits::NONE popup_settings.positioner.size_limits = Limits::NONE
.max_width(372.0) .max_width(372.0)
.min_width(300.0) .min_width(300.0)
.min_height(200.0) .min_height(200.0)
.max_height(1080.0); .max_height(1080.0)
.height(500)
.width(500);
popup_settings.positioner.size = Some((500, 500));
get_popup(popup_settings) get_popup(popup_settings)
} };
} }
Message::PopupClosed(id) => { Message::PopupClosed(id) => {
if self.popup.as_ref() == Some(&id) { if self.popup.as_ref() == Some(&id) {
@ -79,7 +83,7 @@ impl cosmic::Application for Window {
} }
Message::ToggleExampleRow(toggled) => self.example_row = toggled, Message::ToggleExampleRow(toggled) => self.example_row = toggled,
} }
Command::none() Task::none()
} }
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<Self::Message> {
@ -93,15 +97,16 @@ impl cosmic::Application for Window {
fn view_window(&self, _id: Id) -> Element<Self::Message> { fn view_window(&self, _id: Id) -> Element<Self::Message> {
let content_list = list_column().padding(5).spacing(0).add(settings::item( let content_list = list_column().padding(5).spacing(0).add(settings::item(
"Example row", "Example row",
toggler(None, self.example_row, |value| { cosmic::widget::container(toggler(self.example_row, |value| {
Message::ToggleExampleRow(value) Message::ToggleExampleRow(value)
}), }))
.height(Length::Fixed(50.)),
)); ));
self.core.applet.popup_container(content_list).into() self.core.applet.popup_container(content_list).into()
} }
fn style(&self) -> Option<<Theme as application::StyleSheet>::Style> { fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
Some(cosmic::applet::style()) Some(cosmic::applet::style())
} }
} }

View file

@ -11,4 +11,15 @@ tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false default-features = false
features = ["debug", "winit", "tokio", "xdg-portal", "dbus-config", "a11y"] features = [
"debug",
"winit",
"tokio",
"xdg-portal",
"dbus-config",
"a11y",
"wayland",
"wgpu",
"single-instance",
"multi-window",
]

View file

@ -3,7 +3,8 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::widget::column;
use cosmic::iced_core::Size; use cosmic::iced_core::Size;
use cosmic::widget::nav_bar; use cosmic::widget::nav_bar;
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
@ -50,12 +51,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message {} pub enum Message {
Input1(String),
Input2(String),
}
/// The [`App`] stores application-specific state. /// The [`App`] stores application-specific state.
pub struct App { pub struct App {
core: Core, core: Core,
nav_model: nav_bar::Model, nav_model: nav_bar::Model,
input_1: String,
input_2: String,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -81,7 +87,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut nav_model = nav_bar::Model::default(); let mut nav_model = nav_bar::Model::default();
for (title, content) in input { for (title, content) in input {
@ -90,7 +96,12 @@ impl cosmic::Application for App {
nav_model.activate_position(0); nav_model.activate_position(0);
let mut app = App { core, nav_model }; let mut app = App {
core,
nav_model,
input_1: String::new(),
input_2: String::new(),
};
let command = app.update_title(); let command = app.update_title();
@ -103,14 +114,22 @@ impl cosmic::Application for App {
} }
/// Called when a navigation item is selected. /// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Self::Message> { fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
self.nav_model.activate(id); self.nav_model.activate(id);
self.update_title() self.update_title()
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, _message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
Command::none() match message {
Message::Input1(v) => {
self.input_1 = v;
}
Message::Input2(v) => {
self.input_2 = v;
}
}
Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
@ -122,11 +141,20 @@ impl cosmic::Application for App {
let text = cosmic::widget::text(page_content); let text = cosmic::widget::text(page_content);
let centered = cosmic::widget::container(text) let centered = cosmic::widget::container(
column![
text,
cosmic::widget::text_input::text_input("", &self.input_1).on_input(Message::Input1),
cosmic::widget::text_input::text_input("", &self.input_2).on_input(Message::Input2),
]
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Shrink) .height(iced::Length::Shrink)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::alignment::Horizontal::Center),
.align_y(iced::alignment::Vertical::Center); )
.width(iced::Length::Fill)
.height(iced::Length::Shrink)
.align_x(iced::alignment::Horizontal::Center)
.align_y(iced::alignment::Vertical::Center);
Element::from(centered) Element::from(centered)
} }
@ -142,10 +170,14 @@ where
.unwrap_or("Unknown Page") .unwrap_or("Unknown Page")
} }
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
let header_title = self.active_page_title().to_owned(); let header_title = self.active_page_title().to_owned();
let window_title = format!("{header_title} — COSMIC AppDemo"); let window_title = format!("{header_title} — COSMIC AppDemo");
self.set_header_title(header_title); self.set_header_title(header_title);
self.set_window_title(window_title) if let Some(id) = self.core.main_window_id() {
self.set_window_title(window_title, id)
} else {
Task::none()
}
} }
} }

View file

@ -4,7 +4,7 @@
//! Calendar widget example //! Calendar widget example
use chrono::{Local, NaiveDate}; use chrono::{Local, NaiveDate};
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
/// Runs application with these settings /// Runs application with these settings
@ -50,7 +50,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let now = Local::now(); let now = Local::now();
let mut app = App { let mut app = App {
@ -64,7 +64,7 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::DateSelected(date) => { Message::DateSelected(date) => {
self.date_selected = date; self.date_selected = date;
@ -73,7 +73,7 @@ impl cosmic::Application for App {
println!("Date selected: {:?}", self.date_selected); println!("Date selected: {:?}", self.date_selected);
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
@ -99,7 +99,7 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
self.set_header_title(String::from("Calendar Demo")); self.set_header_title(String::from("Calendar Demo"));
self.set_window_title(String::from("Calendar Demo")) self.set_window_title(String::from("Calendar Demo"))
} }

View file

@ -3,7 +3,7 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Task, Core, Settings};
use cosmic::iced_core::Size; use cosmic::iced_core::Size;
use cosmic::widget::{menu, segmented_button}; use cosmic::widget::{menu, segmented_button};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
@ -65,7 +65,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = App {
core, core,
button_label: String::from("Right click me"), button_label: String::from("Right click me"),
@ -80,10 +80,10 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
self.button_label = format!("Clicked {message:?}"); self.button_label = format!("Clicked {message:?}");
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.

View file

@ -6,7 +6,7 @@ use cosmic::{
ThemeBuilder, ThemeBuilder,
}, },
font::load_fonts, font::load_fonts,
iced::{self, Application, Command, Length, Subscription}, iced::{self, Application, Length, Subscription, Task},
iced::{ iced::{
subscription, subscription,
widget::{self, column, container, horizontal_space, row, text}, widget::{self, column, container, horizontal_space, row, text},
@ -324,7 +324,7 @@ impl Application for Window {
type Message = Message; type Message = Message;
type Theme = Theme; type Theme = Theme;
fn new(_flags: ()) -> (Self, Command<Self::Message>) { fn new(_flags: ()) -> (Self, Task<Self::Message>) {
let mut window = Window::default() let mut window = Window::default()
.nav_bar_toggled(true) .nav_bar_toggled(true)
.show_maximize(true) .show_maximize(true)
@ -389,8 +389,8 @@ impl Application for Window {
]) ])
} }
fn update(&mut self, message: Message) -> iced::Command<Self::Message> { fn update(&mut self, message: Message) -> iced::Task<Self::Message> {
let mut ret = Command::none(); let mut ret = Task::none();
match message { match message {
Message::NavBar(key) => { Message::NavBar(key) => {
if let Some(page) = self.nav_id_to_page.get(key).copied() { if let Some(page) = self.nav_id_to_page.get(key).copied() {
@ -437,10 +437,10 @@ impl Application for Window {
Message::ToggleNavBarCondensed => { Message::ToggleNavBarCondensed => {
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed
} }
Message::Drag => return drag(window::Id::MAIN), Message::Drag => return drag(self.core.main_window_id().unwrap()),
Message::Close => return close(window::Id::MAIN), Message::Close => return close(self.core.main_window_id().unwrap()),
Message::Minimize => return minimize(window::Id::MAIN, true), Message::Minimize => return minimize(self.core.main_window_id().unwrap(), true),
Message::Maximize => return toggle_maximize(window::Id::MAIN), Message::Maximize => return toggle_maximize(self.core.main_window_id().unwrap()),
Message::InputChanged => {} Message::InputChanged => {}

View file

@ -482,7 +482,7 @@ impl State {
)) ))
.layer(cosmic::cosmic_theme::Layer::Secondary) .layer(cosmic::cosmic_theme::Layer::Secondary)
.padding(16) .padding(16)
.style(cosmic::theme::Container::Background) .class(cosmic::theme::Container::Background)
.into(), .into(),
cosmic::widget::text_input::secure_input( cosmic::widget::text_input::secure_input(
"Type to search apps or type “?” for more options...", "Type to search apps or type “?” for more options...",

View file

@ -3,7 +3,7 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Task, Core, Settings};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
/// Runs application with these settings /// Runs application with these settings
@ -51,7 +51,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = App {
core, core,
selected: 0, selected: 0,
@ -67,7 +67,7 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::Clicked(id) => self.selected = id, Message::Clicked(id) => self.selected = id,
Message::Remove(id) => { Message::Remove(id) => {
@ -75,7 +75,7 @@ impl cosmic::Application for App {
} }
} }
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
@ -106,7 +106,7 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
self.set_header_title(String::from("Image Button Demo")); self.set_header_title(String::from("Image Button Demo"));
self.set_window_title(String::from("Image Button Demo")) self.set_window_title(String::from("Image Button Demo"))
} }

View file

@ -6,7 +6,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::{env, process}; use std::{env, process};
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Task, Core, Settings};
use cosmic::iced::window; use cosmic::iced::window;
use cosmic::iced_core::alignment::{Horizontal, Vertical}; use cosmic::iced_core::alignment::{Horizontal, Vertical};
use cosmic::iced_core::keyboard::Key; use cosmic::iced_core::keyboard::Key;
@ -97,7 +97,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let app = App { let app = App {
core, core,
config: Config { config: Config {
@ -106,7 +106,7 @@ impl cosmic::Application for App {
key_binds: key_binds(), key_binds: key_binds(),
}; };
(app, Command::none()) (app, Task::none())
} }
fn header_start(&self) -> Vec<Element<Self::Message>> { fn header_start(&self) -> Vec<Element<Self::Message>> {
@ -114,13 +114,13 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::WindowClose => { Message::WindowClose => {
return window::close(window::Id::MAIN); return window::close(self.core.main_window_id().unwrap());
} }
Message::WindowNew => match env::current_exe() { Message::WindowNew => match env::current_exe() {
Ok(exe) => match process::Command::new(&exe).spawn() { Ok(exe) => match process::Task::new(&exe).spawn() {
Ok(_child) => {} Ok(_child) => {}
Err(err) => { Err(err) => {
eprintln!("failed to execute {:?}: {}", exe, err); eprintln!("failed to execute {:?}: {}", exe, err);
@ -132,7 +132,7 @@ impl cosmic::Application for App {
}, },
Message::ToggleHideContent => self.config.hide_content = !self.config.hide_content, Message::ToggleHideContent => self.config.hide_content = !self.config.hide_content,
} }
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.

View file

@ -6,7 +6,7 @@ use cosmic::{
iced_core::{id, Alignment, Length, Point}, iced_core::{id, Alignment, Length, Point},
iced_widget::{column, container, scrollable, text, text_input}, iced_widget::{column, container, scrollable, text, text_input},
widget::{button, header_bar}, widget::{button, header_bar},
ApplicationExt, Command, ApplicationExt, Task,
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -42,10 +42,10 @@ impl cosmic::Application for MultiWindow {
&mut self.core &mut self.core
} }
fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, cosmic::app::Task<Self::Message>) {
let windows = MultiWindow { let windows = MultiWindow {
windows: HashMap::from([( windows: HashMap::from([(
window::Id::MAIN, self.core.main_window_id().unwrap(),
Window { Window {
input_id: id::Id::new("main"), input_id: id::Id::new("main"),
input_value: String::new(), input_value: String::new(),
@ -54,12 +54,12 @@ impl cosmic::Application for MultiWindow {
core, core,
}; };
(windows, cosmic::app::Command::none()) (windows, cosmic::app::Task::none())
} }
fn subscription(&self) -> cosmic::iced_futures::Subscription<Self::Message> { fn subscription(&self) -> cosmic::iced_futures::Subscription<Self::Message> {
event::listen_with(|event, _| { event::listen_with(|event, _, id| {
if let iced::Event::Window(id, window_event) = event { if let iced::Event::Window(window_event) = event {
match window_event { match window_event {
window::Event::CloseRequested => Some(Message::CloseWindow(id)), window::Event::CloseRequested => Some(Message::CloseWindow(id)),
window::Event::Opened { position, .. } => { window::Event::Opened { position, .. } => {
@ -77,18 +77,18 @@ impl cosmic::Application for MultiWindow {
fn update( fn update(
&mut self, &mut self,
message: Self::Message, message: Self::Message,
) -> iced::Command<cosmic::app::Message<Self::Message>> { ) -> iced::Task<cosmic::app::Message<Self::Message>> {
match message { match message {
Message::CloseWindow(id) => window::close(id), Message::CloseWindow(id) => window::close(id),
Message::WindowClosed(id) => { Message::WindowClosed(id) => {
self.windows.remove(&id); self.windows.remove(&id);
Command::none() Task::none()
} }
Message::WindowOpened(id, ..) => { Message::WindowOpened(id, ..) => {
if let Some(window) = self.windows.get(&id) { if let Some(window) = self.windows.get(&id) {
text_input::focus(window.input_id.clone()) text_input::focus(window.input_id.clone())
} else { } else {
Command::none() Task::none()
} }
} }
Message::NewWindow => { Message::NewWindow => {
@ -113,13 +113,13 @@ impl cosmic::Application for MultiWindow {
spawn_window spawn_window
} }
Message::Input(id, value) => { Message::Input(id, value) => {
if let Some(w) = self.windows.get_mut(&window::Id::MAIN) { if let Some(w) = self.windows.get_mut(&self.core.main_window_id().unwrap()) {
if id == w.input_id { if id == w.input_id {
w.input_value = value; w.input_value = value;
} }
} }
Command::none() Task::none()
} }
} }
} }
@ -142,17 +142,15 @@ impl cosmic::Application for MultiWindow {
column![input, new_window_button] column![input, new_window_button]
.spacing(50) .spacing(50)
.width(Length::Fill) .width(Length::Fill)
.align_items(Alignment::Center), .align_x(Alignment::Center),
); );
let window_content = container(container(content).width(200).center_x()) let window_content = container(container(content).center_x(Length::Fixed(200.)))
.style(cosmic::style::Container::Background) .style(cosmic::style::Container::Background)
.width(Length::Fill) .center_x(Length::Fill)
.height(Length::Fill) .center_y(Length::Fill);
.center_x()
.center_y();
if id == window::Id::MAIN { if id == self.core.main_window_id().unwrap() {
window_content.into() window_content.into()
} else { } else {
column![header_bar().focused(focused), window_content].into() column![header_bar().focused(focused), window_content].into()
@ -160,6 +158,6 @@ impl cosmic::Application for MultiWindow {
} }
fn view(&self) -> cosmic::prelude::Element<Self::Message> { fn view(&self) -> cosmic::prelude::Element<Self::Message> {
self.view_window(window::Id::MAIN) self.view_window(self.core.main_window_id().unwrap())
} }
} }

View file

@ -5,7 +5,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced_core::Size; use cosmic::iced_core::Size;
use cosmic::widget::{menu, nav_bar}; use cosmic::widget::{menu, nav_bar};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
@ -106,7 +106,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut nav_model = nav_bar::Model::default(); let mut nav_model = nav_bar::Model::default();
for (title, content) in input { for (title, content) in input {
@ -143,13 +143,13 @@ impl cosmic::Application for App {
} }
/// Called when a navigation item is selected. /// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Self::Message> { fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
self.nav_model.activate(id); self.nav_model.activate(id);
self.update_title() self.update_title()
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::NavMenuAction(message) => match message { Message::NavMenuAction(message) => match message {
NavMenuAction::Delete(id) => self.nav_model.remove(id), NavMenuAction::Delete(id) => self.nav_model.remove(id),
@ -168,7 +168,7 @@ impl cosmic::Application for App {
}, },
} }
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
@ -200,7 +200,7 @@ where
.unwrap_or("Unknown Page") .unwrap_or("Unknown Page")
} }
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
let header_title = self.active_page_title().to_owned(); let header_title = self.active_page_title().to_owned();
let window_title = format!("{header_title} — COSMIC AppDemo"); let window_title = format!("{header_title} — COSMIC AppDemo");
self.set_header_title(header_title); self.set_header_title(header_title);

View file

@ -4,7 +4,7 @@
//! An application which provides an open dialog //! An application which provides an open dialog
use apply::Apply; use apply::Apply;
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Task, Core, Settings};
use cosmic::dialog::file_chooser::{self, FileFilter}; use cosmic::dialog::file_chooser::{self, FileFilter};
use cosmic::iced_core::Length; use cosmic::iced_core::Length;
use cosmic::widget::button; use cosmic::widget::button;
@ -66,7 +66,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = App {
core, core,
file_contents: String::new(), file_contents: String::new(),
@ -77,7 +77,7 @@ impl cosmic::Application for App {
app.set_header_title("Open a file".into()); app.set_header_title("Open a file".into());
let cmd = app.set_window_title( let cmd = app.set_window_title(
"COSMIC OpenDialog Demo".into(), "COSMIC OpenDialog Demo".into(),
cosmic::iced::window::Id::MAIN, cosmic::iced::self.core.main_window_id().unwrap(),
); );
(app, cmd) (app, cmd)
@ -88,7 +88,7 @@ impl cosmic::Application for App {
vec![button::suggested("Open").on_press(Message::OpenFile).into()] vec![button::suggested("Open").on_press(Message::OpenFile).into()]
} }
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::Cancelled => { Message::Cancelled => {
eprintln!("open file dialog cancelled"); eprintln!("open file dialog cancelled");
@ -198,7 +198,7 @@ impl cosmic::Application for App {
} }
} }
Command::none() Task::none()
} }
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<Self::Message> {

View file

@ -3,7 +3,7 @@
//! Application API example //! Application API example
use cosmic::app::{Command, Core, Settings}; use cosmic::app::{Core, Settings, Task};
use cosmic::{executor, iced, ApplicationExt, Element}; use cosmic::{executor, iced, ApplicationExt, Element};
/// Runs application with these settings /// Runs application with these settings
@ -55,7 +55,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, _input: Self::Flags) -> (Self, Command<Self::Message>) { fn init(core: Core, _input: Self::Flags) -> (Self, Task<Self::Message>) {
let mut app = App { let mut app = App {
core, core,
editing: false, editing: false,
@ -63,7 +63,7 @@ impl cosmic::Application for App {
search_id: cosmic::widget::Id::unique(), search_id: cosmic::widget::Id::unique(),
}; };
let commands = Command::batch(vec![ let commands = Task::batch(vec![
cosmic::widget::text_input::focus(app.search_id.clone()), cosmic::widget::text_input::focus(app.search_id.clone()),
app.update_title(), app.update_title(),
]); ]);
@ -72,7 +72,7 @@ impl cosmic::Application for App {
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::Input(text) => { Message::Input(text) => {
self.input = text; self.input = text;
@ -83,7 +83,7 @@ impl cosmic::Application for App {
} }
} }
Command::none() Task::none()
} }
/// Creates a view after each update. /// Creates a view after each update.
@ -115,7 +115,7 @@ impl App
where where
Self: cosmic::Application, Self: cosmic::Application,
{ {
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
let window_title = format!("COSMIC TextInputs Demo"); let window_title = format!("COSMIC TextInputs Demo");
self.set_header_title(window_title.clone()); self.set_header_title(window_title.clone());
self.set_window_title(window_title) self.set_window_title(window_title)

2
iced

@ -1 +1 @@
Subproject commit 061995084a5775b4fd7df63dda336be01ddf491c Subproject commit f2f9dfc6c37e14c4f8ff5dafe70d72d3c40eb692

View file

@ -7,62 +7,88 @@ use iced::window;
use super::Message; use super::Message;
/// Commands for COSMIC applications. /// Commands for COSMIC applications.
pub type Command<M> = iced::Command<Message<M>>; pub type Task<M> = iced::Task<Message<M>>;
/// Creates a command which yields a [`crate::app::Message`]. /// Creates a command which yields a [`crate::app::Message`].
pub fn message<M: Send + 'static>(message: Message<M>) -> Command<M> { pub fn message<M: Send + 'static>(message: Message<M>) -> Task<M> {
crate::command::message(message) crate::command::message(message)
} }
/// Convenience methods for building message-based commands. /// Convenience methods for building message-based commands.
pub mod message { pub mod message {
/// Creates a command which yields an application message. /// Creates a command which yields an application message.
pub fn app<M: Send + 'static>(message: M) -> crate::app::Command<M> { pub fn app<M: Send + 'static>(message: M) -> crate::app::Task<M> {
super::message(super::Message::App(message)) super::message(super::Message::App(message))
} }
/// Creates a command which yields a cosmic message. /// Creates a command which yields a cosmic message.
pub fn cosmic<M: Send + 'static>( pub fn cosmic<M: Send + 'static>(message: crate::app::cosmic::Message) -> crate::app::Task<M> {
message: crate::app::cosmic::Message,
) -> crate::app::Command<M> {
super::message(super::Message::Cosmic(message)) super::message(super::Message::Cosmic(message))
} }
} }
pub fn drag<M: Send + 'static>(id: Option<window::Id>) -> iced::Command<Message<M>> { impl crate::app::Core {
crate::command::drag(id).map(Message::Cosmic) pub fn drag<M: Send + 'static>(&self, id: Option<window::Id>) -> iced::Task<Message<M>> {
let Some(id) = id.or(self.main_window.get().cloned()) else {
return iced::Task::none();
};
crate::command::drag(id).map(Message::Cosmic)
}
pub fn maximize<M: Send + 'static>(
&self,
id: Option<window::Id>,
maximized: bool,
) -> iced::Task<Message<M>> {
let Some(id) = id.or(self.main_window.get().cloned()) else {
return iced::Task::none();
};
crate::command::maximize(id, maximized).map(Message::Cosmic)
}
pub fn minimize<M: Send + 'static>(&self, id: Option<window::Id>) -> iced::Task<Message<M>> {
let Some(id) = id.or(self.main_window.get().cloned()) else {
return iced::Task::none();
};
crate::command::minimize(id).map(Message::Cosmic)
}
pub fn set_scaling_factor<M: Send + 'static>(&self, factor: f32) -> iced::Task<Message<M>> {
message::cosmic(super::cosmic::Message::ScaleFactor(factor))
}
pub fn set_title<M: Send + 'static>(
&self,
id: Option<window::Id>,
title: String,
) -> iced::Task<Message<M>> {
let Some(id) = id.or(self.main_window.get().cloned()) else {
return iced::Task::none();
};
crate::command::set_title(id, title).map(Message::Cosmic)
}
pub fn set_windowed<M: Send + 'static>(
&self,
id: Option<window::Id>,
) -> iced::Task<Message<M>> {
let Some(id) = id.or(self.main_window.get().cloned()) else {
return iced::Task::none();
};
crate::command::set_windowed(id).map(Message::Cosmic)
}
pub fn toggle_maximize<M: Send + 'static>(
&self,
id: Option<window::Id>,
) -> iced::Task<Message<M>> {
let Some(id) = id.or(self.main_window.get().cloned()) else {
return iced::Task::none();
};
crate::command::toggle_maximize(id).map(Message::Cosmic)
}
} }
pub fn maximize<M: Send + 'static>( pub fn set_theme<M: Send + 'static>(theme: crate::Theme) -> iced::Task<Message<M>> {
id: Option<window::Id>,
maximized: bool,
) -> iced::Command<Message<M>> {
crate::command::maximize(id, maximized).map(Message::Cosmic)
}
pub fn minimize<M: Send + 'static>(id: Option<window::Id>) -> iced::Command<Message<M>> {
crate::command::minimize(id).map(Message::Cosmic)
}
pub fn set_scaling_factor<M: Send + 'static>(factor: f32) -> iced::Command<Message<M>> {
message::cosmic(super::cosmic::Message::ScaleFactor(factor))
}
pub fn set_theme<M: Send + 'static>(theme: crate::Theme) -> iced::Command<Message<M>> {
message::cosmic(super::cosmic::Message::AppThemeChange(theme)) message::cosmic(super::cosmic::Message::AppThemeChange(theme))
} }
pub fn set_title<M: Send + 'static>(
id: Option<window::Id>,
title: String,
) -> iced::Command<Message<M>> {
crate::command::set_title(id, title).map(Message::Cosmic)
}
pub fn set_windowed<M: Send + 'static>(id: Option<window::Id>) -> iced::Command<Message<M>> {
crate::command::set_windowed(id).map(Message::Cosmic)
}
pub fn toggle_maximize<M: Send + 'static>(id: Option<window::Id>) -> iced::Command<Message<M>> {
crate::command::toggle_maximize(id).map(Message::Cosmic)
}

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::collections::HashMap; use std::{cell::OnceCell, collections::HashMap};
use crate::widget::nav_bar; use crate::widget::nav_bar;
use cosmic_config::CosmicConfigEntry; use cosmic_config::CosmicConfigEntry;
@ -40,8 +40,8 @@ pub struct Window {
pub show_close: bool, pub show_close: bool,
pub show_maximize: bool, pub show_maximize: bool,
pub show_minimize: bool, pub show_minimize: bool,
height: u32, height: f32,
width: u32, width: f32,
} }
/// COSMIC-specific application settings /// COSMIC-specific application settings
@ -93,6 +93,10 @@ pub struct Core {
#[cfg(feature = "dbus-config")] #[cfg(feature = "dbus-config")]
pub(crate) settings_daemon: Option<cosmic_settings_daemon::CosmicSettingsDaemonProxy<'static>>, pub(crate) settings_daemon: Option<cosmic_settings_daemon::CosmicSettingsDaemonProxy<'static>>,
pub(crate) main_window: OnceCell<window::Id>,
pub(crate) exit_on_main_window_closed: bool,
} }
impl Default for Core { impl Default for Core {
@ -135,10 +139,10 @@ impl Default for Core {
show_maximize: true, show_maximize: true,
show_minimize: true, show_minimize: true,
show_window_menu: false, show_window_menu: false,
height: 0, height: 0.,
width: 0, width: 0.,
}, },
focused_window: Some(window::Id::MAIN), focused_window: None,
#[cfg(feature = "applet")] #[cfg(feature = "applet")]
applet: crate::applet::Context::default(), applet: crate::applet::Context::default(),
#[cfg(feature = "single-instance")] #[cfg(feature = "single-instance")]
@ -148,6 +152,8 @@ impl Default for Core {
portal_is_dark: None, portal_is_dark: None,
portal_accent: None, portal_accent: None,
portal_is_high_contrast: None, portal_is_high_contrast: None,
main_window: OnceCell::new(),
exit_on_main_window_closed: true,
} }
} }
} }
@ -297,12 +303,12 @@ impl Core {
} }
/// Set the height of the main window. /// Set the height of the main window.
pub(crate) fn set_window_height(&mut self, new_height: u32) { pub(crate) fn set_window_height(&mut self, new_height: f32) {
self.window.height = new_height; self.window.height = new_height;
} }
/// Set the width of the main window. /// Set the width of the main window.
pub(crate) fn set_window_width(&mut self, new_width: u32) { pub(crate) fn set_window_width(&mut self, new_width: f32) {
self.window.width = new_width; self.window.width = new_width;
self.is_condensed_update(); self.is_condensed_update();
} }
@ -364,4 +370,12 @@ impl Core {
self.portal_is_dark self.portal_is_dark
.unwrap_or(self.system_theme_mode.is_dark) .unwrap_or(self.system_theme_mode.is_dark)
} }
/// The [`Id`] of the main window
pub fn main_window_id(&self) -> Option<window::Id> {
self.main_window
.get()
.filter(|id| iced::window::Id::NONE != **id)
.cloned()
}
} }

View file

@ -1,32 +1,23 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::borrow::Borrow;
use std::sync::Arc; use std::sync::Arc;
use super::{command, Application, ApplicationExt, Core, Subscription}; use super::{Application, ApplicationExt, Core, Subscription};
use crate::config::CosmicTk; use crate::config::CosmicTk;
use crate::theme::{self, Theme, ThemeType, THEME}; use crate::theme::{Theme, ThemeType, THEME};
use crate::widget::nav_bar; use crate::widget::nav_bar;
use crate::{keyboard_nav, Element}; use crate::{keyboard_nav, Element};
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
use cosmic_theme::ThemeMode; use cosmic_theme::ThemeMode;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use iced::event::wayland::{self, WindowEvent}; use iced::event::wayland;
#[cfg(feature = "wayland")]
use iced::event::PlatformSpecific;
#[cfg(all(feature = "winit", feature = "multi-window"))]
use iced::multi_window::Application as IcedApplication;
#[cfg(feature = "wayland")]
use iced::wayland::Application as IcedApplication;
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(any(feature = "multi-window", feature = "wayland")))]
use iced::Application as IcedApplication; use iced::Application as IcedApplication;
use iced::{window, Command}; use iced::{window, Task};
use iced_futures::event::listen_with; use iced_futures::event::listen_with;
#[cfg(not(feature = "wayland"))]
use iced_runtime::command::Action;
#[cfg(not(feature = "wayland"))]
use iced_runtime::window::Action as WindowAction;
use palette::color_difference::EuclideanDistance; use palette::color_difference::EuclideanDistance;
/// A message managed internally by COSMIC. /// A message managed internally by COSMIC.
@ -65,7 +56,7 @@ pub enum Message {
/// Updates the window maximized state /// Updates the window maximized state
WindowMaximized(window::Id, bool), WindowMaximized(window::Id, bool),
/// Updates the tracked window geometry. /// Updates the tracked window geometry.
WindowResize(window::Id, u32, u32), WindowResize(window::Id, f32, f32),
/// Tracks updates to window state. /// Tracks updates to window state.
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
WindowState(window::Id, WindowState), WindowState(window::Id, WindowState),
@ -86,7 +77,9 @@ pub enum Message {
Unfocus(window::Id), Unfocus(window::Id),
/// Tracks updates to window suggested size. /// Tracks updates to window suggested size.
#[cfg(feature = "applet")] #[cfg(feature = "applet")]
Configure(cctk::sctk::shell::xdg::window::WindowConfigure), SuggestedBounds(Option<iced::Size>),
/// Window Created
MainWindowCreated(window::Id),
} }
#[derive(Default)] #[derive(Default)]
@ -94,109 +87,104 @@ pub struct Cosmic<App> {
pub app: App, pub app: App,
} }
impl<T: Application> IcedApplication for Cosmic<T> impl<T: Application> Cosmic<T>
where where
T::Message: Send + 'static, T::Message: Send + 'static,
{ {
type Executor = T::Executor; pub fn init(
type Flags = (Core, T::Flags); (mut core, flags, window_settings): (Core, T::Flags, iced::window::Settings),
type Message = super::Message<T::Message>; ) -> (Self, iced::Task<super::Message<T::Message>>) {
type Theme = Theme;
fn new((mut core, flags): Self::Flags) -> (Self, iced::Command<Self::Message>) {
#[cfg(feature = "dbus-config")] #[cfg(feature = "dbus-config")]
{ {
use iced_futures::futures::executor::block_on; use iced_futures::futures::executor::block_on;
core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok(); core.settings_daemon = block_on(cosmic_config::dbus::settings_daemon_proxy()).ok();
} }
let (model, command) = T::init(core, flags); let (model, mut command) = T::init(core, flags);
(Self::new(model), command) (Self::new(model), command)
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn title(&self) -> String { pub fn title(&self) -> String {
self.app.title().to_string() self.app.title().to_string()
} }
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
fn title(&self, id: window::Id) -> String { pub fn title(&self, id: window::Id) -> String {
self.app.title(id).to_string() self.app.title(id).to_string()
} }
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> { pub fn update(
&mut self,
message: super::Message<T::Message>,
) -> iced::Task<super::Message<T::Message>> {
match message { match message {
super::Message::App(message) => self.app.update(message), super::Message::App(message) => self.app.update(message),
super::Message::Cosmic(message) => self.cosmic_update(message), super::Message::Cosmic(message) => self.cosmic_update(message),
super::Message::None => iced::Command::none(), super::Message::None => iced::Task::none(),
#[cfg(feature = "single-instance")] #[cfg(feature = "single-instance")]
super::Message::DbusActivation(message) => self.app.dbus_activation(message), super::Message::DbusActivation(message) => self.app.dbus_activation(message),
} }
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
f64::from(self.app.core().scale_factor()) f64::from(self.app.core().scale_factor())
} }
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
fn scale_factor(&self, _id: window::Id) -> f64 { pub fn scale_factor(&self, _id: window::Id) -> f64 {
f64::from(self.app.core().scale_factor()) f64::from(self.app.core().scale_factor())
} }
fn style(&self) -> <Self::Theme as iced_style::application::StyleSheet>::Style { pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance {
if let Some(style) = self.app.style() { if let Some(style) = self.app.style() {
style style
} else if self.app.core().window.sharp_corners { } else if self.app.core().window.sharp_corners {
theme::Application::default() let theme = THEME.lock().unwrap();
crate::style::iced::application::appearance(theme.borrow())
} else { } else {
theme::Application::Custom(Box::new(|theme| iced_style::application::Appearance { let theme = THEME.lock().unwrap();
iced_runtime::Appearance {
background_color: iced_core::Color::TRANSPARENT, background_color: iced_core::Color::TRANSPARENT,
icon_color: theme.cosmic().on_bg_color().into(), icon_color: theme.cosmic().on_bg_color().into(),
text_color: theme.cosmic().on_bg_color().into(), text_color: theme.cosmic().on_bg_color().into(),
})) }
} }
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn subscription(&self) -> Subscription<Self::Message> { pub fn subscription(&self) -> Subscription<super::Message<T::Message>> {
let window_events = listen_with(|event, _| { let window_events = listen_with(|event, _, id| {
match event { match event {
iced::Event::Window(id, window::Event::Resized { width, height }) => { iced::Event::Window(window::Event::Resized(iced::Size { width, height })) => {
return Some(Message::WindowResize(id, width, height)); return Some(Message::WindowResize(id, width, height));
} }
iced::Event::Window(id, window::Event::Closed) => { iced::Event::Window(window::Event::Closed) => {
return Some(Message::SurfaceClosed(id)) return Some(Message::SurfaceClosed(id))
} }
iced::Event::Window(id, window::Event::Focused) => return Some(Message::Focus(id)), iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)),
iced::Event::Window(id, window::Event::Unfocused) => { iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)),
return Some(Message::Unfocus(id)) iced::Event::Window(window::Event::Opened { .. }) => {
return Some(Message::MainWindowCreated(id));
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
iced::Event::PlatformSpecific(PlatformSpecific::Wayland(event)) => match event { iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
wayland::Event::Window(WindowEvent::State(state), _surface, id) => { match event {
return Some(Message::WindowState(id, state)); wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
| wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
return Some(Message::SurfaceClosed(id));
}
#[cfg(feature = "applet")]
wayland::Event::Window(
iced::event::wayland::WindowEvent::SuggestedBounds(b),
) => {
return Some(Message::SuggestedBounds(b));
}
_ => (),
} }
wayland::Event::Window( }
WindowEvent::WmCapabilities(capabilities),
_surface,
id,
) => {
return Some(Message::WmCapabilities(id, capabilities));
}
wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
| wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
return Some(Message::SurfaceClosed(id));
}
#[cfg(feature = "applet")]
wayland::Event::Window(WindowEvent::Configure(conf), _surface, id)
if id == window::Id::MAIN =>
{
return Some(Message::Configure(conf));
}
_ => (),
},
_ => (), _ => (),
} }
@ -275,19 +263,24 @@ where
Subscription::batch(subscriptions) Subscription::batch(subscriptions)
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn theme(&self) -> Self::Theme { pub fn theme(&self) -> Theme {
crate::theme::active() crate::theme::active()
} }
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
fn theme(&self, _id: window::Id) -> Self::Theme { pub fn theme(&self, _id: window::Id) -> Theme {
crate::theme::active() crate::theme::active()
} }
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
fn view(&self, id: window::Id) -> Element<Self::Message> { pub fn view(&self, id: window::Id) -> Element<super::Message<T::Message>> {
if id != self.app.main_window_id() { if !self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
return self.app.view_window(id).map(super::Message::App); return self.app.view_window(id).map(super::Message::App);
} }
@ -298,37 +291,43 @@ where
} }
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn view(&self) -> Element<Self::Message> { pub fn view(&self) -> Element<super::Message<T::Message>> {
self.app.view_main() self.app.view_main()
} }
} }
impl<T: Application> Cosmic<T> { impl<T: Application> Cosmic<T> {
#[cfg(feature = "wayland")]
pub fn close(&mut self) -> iced::Command<super::Message<T::Message>> {
iced_sctk::commands::window::close_window(self.app.main_window_id())
}
#[cfg(not(feature = "wayland"))]
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
pub fn close(&mut self) -> iced::Command<super::Message<T::Message>> { pub fn close(&mut self) -> iced::Task<super::Message<T::Message>> {
iced::Command::single(Action::Window(WindowAction::Close( if let Some(id) = self.app.core().main_window_id() {
self.app.main_window_id(), iced::window::close(id)
))) } else {
iced::Task::none()
}
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn cosmic_update(&mut self, message: Message) -> iced::Command<super::Message<T::Message>> { fn cosmic_update(&mut self, message: Message) -> iced::Task<super::Message<T::Message>> {
match message { match message {
Message::WindowMaximized(id, maximized) => { Message::WindowMaximized(id, maximized) => {
if self.app.main_window_id() == id { if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().window.sharp_corners = maximized; self.app.core_mut().window.sharp_corners = maximized;
} }
} }
Message::WindowResize(id, width, height) => { Message::WindowResize(id, width, height) => {
if self.app.main_window_id() == id { if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().set_window_width(width); self.app.core_mut().set_window_width(width);
self.app.core_mut().set_window_height(height); self.app.core_mut().set_window_height(height);
} }
@ -336,15 +335,19 @@ impl<T: Application> Cosmic<T> {
self.app.on_window_resize(id, width, height); self.app.on_window_resize(id, width, height);
//TODO: more efficient test of maximized (winit has no event for maximize if set by the OS) //TODO: more efficient test of maximized (winit has no event for maximize if set by the OS)
#[cfg(not(feature = "wayland"))] return iced::window::get_maximized(id).map(move |maximized| {
return iced::window::fetch_maximized(id, move |maximized| {
super::Message::Cosmic(Message::WindowMaximized(id, maximized)) super::Message::Cosmic(Message::WindowMaximized(id, maximized))
}); });
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Message::WindowState(id, state) => { Message::WindowState(id, state) => {
if self.app.main_window_id() == id { if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().window.sharp_corners = state.intersects( self.app.core_mut().window.sharp_corners = state.intersects(
WindowState::MAXIMIZED WindowState::MAXIMIZED
| WindowState::FULLSCREEN | WindowState::FULLSCREEN
@ -359,7 +362,12 @@ impl<T: Application> Cosmic<T> {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Message::WmCapabilities(id, capabilities) => { Message::WmCapabilities(id, capabilities) => {
if self.app.main_window_id() == id { if self
.app
.core()
.main_window_id()
.is_some_and(|main_id| main_id == id)
{
self.app.core_mut().window.show_maximize = self.app.core_mut().window.show_maximize =
capabilities.contains(WindowManagerCapabilities::MAXIMIZE); capabilities.contains(WindowManagerCapabilities::MAXIMIZE);
self.app.core_mut().window.show_minimize = self.app.core_mut().window.show_minimize =
@ -379,9 +387,7 @@ impl<T: Application> Cosmic<T> {
keyboard_nav::Message::Escape => return self.app.on_escape(), keyboard_nav::Message::Escape => return self.app.on_escape(),
keyboard_nav::Message::Search => return self.app.on_search(), keyboard_nav::Message::Search => return self.app.on_search(),
keyboard_nav::Message::Fullscreen => { keyboard_nav::Message::Fullscreen => return self.app.core().toggle_maximize(None),
return command::toggle_maximize(Some(self.app.main_window_id()))
}
}, },
Message::ContextDrawer(show) => { Message::ContextDrawer(show) => {
@ -389,11 +395,11 @@ impl<T: Application> Cosmic<T> {
return self.app.on_context_drawer(); return self.app.on_context_drawer();
} }
Message::Drag => return command::drag(Some(self.app.main_window_id())), Message::Drag => return self.app.core().drag(None),
Message::Minimize => return command::minimize(Some(self.app.main_window_id())), Message::Minimize => return self.app.core().minimize(None),
Message::Maximize => return command::toggle_maximize(Some(self.app.main_window_id())), Message::Maximize => return self.app.core().toggle_maximize(None),
Message::NavBar(key) => { Message::NavBar(key) => {
self.app.core_mut().nav_bar_set_toggled_condensed(false); self.app.core_mut().nav_bar_set_toggled_condensed(false);
@ -433,7 +439,7 @@ impl<T: Application> Cosmic<T> {
let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark(); let cur_is_dark = THEME.lock().unwrap().theme_type.is_dark();
// Ignore updates if the current theme mode does not match. // Ignore updates if the current theme mode does not match.
if cur_is_dark != theme.cosmic().is_dark { if cur_is_dark != theme.cosmic().is_dark {
return iced::Command::none(); return iced::Task::none();
} }
let cmd = self.app.system_theme_update(&keys, theme.cosmic()); let cmd = self.app.system_theme_update(&keys, theme.cosmic());
// Record the last-known system theme in event that the current theme is custom. // Record the last-known system theme in event that the current theme is custom.
@ -479,7 +485,7 @@ impl<T: Application> Cosmic<T> {
} }
Message::SystemThemeModeChange(keys, mode) => { Message::SystemThemeModeChange(keys, mode) => {
if !keys.contains(&"is_dark") { if !keys.contains(&"is_dark") {
return iced::Command::none(); return iced::Task::none();
} }
if match THEME.lock().unwrap().theme_type { if match THEME.lock().unwrap().theme_type {
ThemeType::System { ThemeType::System {
@ -488,7 +494,7 @@ impl<T: Application> Cosmic<T> {
} => prefer_dark.is_some(), } => prefer_dark.is_some(),
_ => false, _ => false,
} { } {
return iced::Command::none(); return iced::Task::none();
} }
let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)]; let mut cmds = vec![self.app.system_theme_mode_update(&keys, &mode)];
@ -527,23 +533,37 @@ impl<T: Application> Cosmic<T> {
} }
} }
} }
return Command::batch(cmds); return Task::batch(cmds);
} }
Message::Activate(_token) => { Message::Activate(_token) =>
{
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
return iced_sctk::commands::activation::activate( if let Some(id) = self.app.core().main_window_id() {
self.app.main_window_id(), return iced_winit::platform_specific::commands::activation::activate(
#[allow(clippy::used_underscore_binding)] id,
_token, #[allow(clippy::used_underscore_binding)]
); _token,
} );
Message::SurfaceClosed(id) => {
if let Some(msg) = self.app.on_close_requested(id) {
return self.app.update(msg);
} }
} }
Message::SurfaceClosed(id) => {
let mut ret = if let Some(msg) = self.app.on_close_requested(id) {
self.app.update(msg)
} else {
Task::none()
};
let core = self.app.core();
if core.exit_on_main_window_closed
&& core.main_window_id().is_some_and(|m_id| id == m_id)
{
ret = Task::batch(vec![iced::exit::<super::Message<T::Message>>()]);
}
return ret;
}
Message::ShowWindowMenu => { Message::ShowWindowMenu => {
return window::show_window_menu(window::Id::MAIN); if let Some(id) = self.app.core().main_window_id() {
return iced::window::show_system_menu(id);
}
} }
#[cfg(feature = "xdg-portal")] #[cfg(feature = "xdg-portal")]
Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => { Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
@ -555,7 +575,7 @@ impl<T: Application> Cosmic<T> {
} => prefer_dark.is_some(), } => prefer_dark.is_some(),
_ => false, _ => false,
} { } {
return iced::Command::none(); return iced::Task::none();
} }
let is_dark = match s { let is_dark = match s {
ColorScheme::NoPreference => None, ColorScheme::NoPreference => None,
@ -595,7 +615,7 @@ impl<T: Application> Cosmic<T> {
if cur_accent.distance_squared(*c) < 0.00001 { if cur_accent.distance_squared(*c) < 0.00001 {
// skip calculations if we already have the same color // skip calculations if we already have the same color
return iced::Command::none(); return iced::Task::none();
} }
{ {
@ -640,19 +660,20 @@ impl<T: Application> Cosmic<T> {
core.focused_window = None; core.focused_window = None;
} }
} }
#[cfg(feature = "applet")] Message::MainWindowCreated(id) => {
Message::Configure(configure) => { let core = self.app.core_mut();
if let Some(w) = configure.new_size.0 { _ = core.main_window.set(id);
self.app.core_mut().set_window_width(w.get());
}
if let Some(h) = configure.new_size.1 {
self.app.core_mut().set_window_height(h.get());
}
self.app.core_mut().applet.configure = Some(configure);
} }
#[cfg(feature = "applet")]
Message::SuggestedBounds(b) => {
tracing::info!("Suggested bounds: {b:?}");
let core = self.app.core_mut();
core.applet.suggested_bounds = b;
}
_ => {}
} }
iced::Command::none() iced::Task::none()
} }
} }

View file

@ -9,6 +9,8 @@
pub mod command; pub mod command;
mod core; mod core;
pub mod cosmic; pub mod cosmic;
#[cfg(all(feature = "winit", feature = "multi-window"))]
pub(crate) mod multi_window;
pub mod settings; pub mod settings;
pub mod message { pub mod message {
@ -47,17 +49,14 @@ pub mod message {
use std::borrow::Cow; use std::borrow::Cow;
pub use self::command::Command; pub use self::command::Task;
pub use self::core::Core; pub use self::core::Core;
pub use self::settings::Settings; pub use self::settings::Settings;
use crate::prelude::*; use crate::prelude::*;
use crate::theme::THEME; use crate::theme::THEME;
use crate::widget::{context_drawer, horizontal_space, id_container, menu, nav_bar, popover}; use crate::widget::{context_drawer, horizontal_space, id_container, menu, nav_bar, popover};
use apply::Apply; use apply::Apply;
#[cfg(all(feature = "winit", feature = "multi-window"))] use iced::window;
use iced::{multi_window::Application as IcedApplication, window};
#[cfg(any(not(feature = "winit"), not(feature = "multi-window")))]
use iced::{window, Application as IcedApplication};
use iced::{Length, Subscription}; use iced::{Length, Subscription};
pub use message::Message; pub use message::Message;
use url::Url; use url::Url;
@ -73,15 +72,15 @@ use {
pub(crate) fn iced_settings<App: Application>( pub(crate) fn iced_settings<App: Application>(
settings: Settings, settings: Settings,
flags: App::Flags, flags: App::Flags,
) -> iced::Settings<(Core, App::Flags)> { ) -> (iced::Settings, (Core, App::Flags, iced::window::Settings)) {
preload_fonts(); preload_fonts();
let mut core = Core::default(); let mut core = Core::default();
core.debug = settings.debug; core.debug = settings.debug;
core.icon_theme_override = settings.default_icon_theme.is_some(); core.icon_theme_override = settings.default_icon_theme.is_some();
core.set_scale_factor(settings.scale_factor); core.set_scale_factor(settings.scale_factor);
core.set_window_width(settings.size.width as u32); core.set_window_width(settings.size.width);
core.set_window_height(settings.size.height as u32); core.set_window_height(settings.size.height);
if let Some(icon_theme) = settings.default_icon_theme { if let Some(icon_theme) = settings.default_icon_theme {
crate::icon_theme::set_default(icon_theme); crate::icon_theme::set_default(icon_theme);
@ -91,65 +90,40 @@ pub(crate) fn iced_settings<App: Application>(
THEME.lock().unwrap().set_theme(settings.theme.theme_type); THEME.lock().unwrap().set_theme(settings.theme.theme_type);
let mut iced = iced::Settings::with_flags((core, flags)); if settings.no_main_window {
core.main_window.set(iced::window::Id::NONE).unwrap();
}
let mut iced = iced::Settings::default();
iced.antialiasing = settings.antialiasing; iced.antialiasing = settings.antialiasing;
iced.default_font = settings.default_font; iced.default_font = settings.default_font;
iced.default_text_size = iced::Pixels(settings.default_text_size); iced.default_text_size = iced::Pixels(settings.default_text_size);
iced.exit_on_close_request = settings.exit_on_close; let exit_on_close = settings.exit_on_close;
#[cfg(not(feature = "wayland"))] iced.exit_on_close_request = exit_on_close;
{ let mut window_settings = iced::window::Settings::default();
let exit_on_close = settings.exit_on_close; window_settings.exit_on_close_request = exit_on_close;
iced.window.exit_on_close_request = exit_on_close;
}
iced.id = Some(App::APP_ID.to_owned()); iced.id = Some(App::APP_ID.to_owned());
#[cfg(all(not(feature = "wayland"), target_os = "linux"))] window_settings.platform_specific.application_id = App::APP_ID.to_string();
{ core.exit_on_main_window_closed = exit_on_close;
iced.window.platform_specific.application_id = App::APP_ID.to_string();
if let Some(border_size) = settings.resizable {
window_settings.resize_border = border_size as u32;
window_settings.resizable = true;
}
window_settings.decorations = !settings.client_decorations;
window_settings.size = settings.size;
let min_size = settings.size_limits.min();
if min_size != iced::Size::ZERO {
window_settings.min_size = Some(min_size);
}
let max_size = settings.size_limits.max();
if max_size != iced::Size::INFINITY {
window_settings.max_size = Some(max_size);
} }
#[cfg(feature = "wayland")] window_settings.transparent = settings.transparent;
{ (iced, (core, flags, window_settings))
use iced::wayland::actions::window::SctkWindowSettings;
use iced_sctk::settings::InitialSurface;
iced.initial_surface = if settings.no_main_window {
InitialSurface::None
} else {
InitialSurface::XdgWindow(SctkWindowSettings {
app_id: Some(App::APP_ID.to_owned()),
autosize: settings.autosize,
client_decorations: settings.client_decorations,
resizable: settings.resizable,
size: (settings.size.width as u32, settings.size.height as u32).into(),
size_limits: settings.size_limits,
title: None,
transparent: settings.transparent,
xdg_activation_token: std::env::var("XDG_ACTIVATION_TOKEN").ok(),
..SctkWindowSettings::default()
})
};
}
#[cfg(not(feature = "wayland"))]
{
if let Some(border_size) = settings.resizable {
iced.window.resize_border = border_size as u32;
iced.window.resizable = true;
}
iced.window.decorations = !settings.client_decorations;
iced.window.size = settings.size;
let min_size = settings.size_limits.min();
if min_size != iced::Size::ZERO {
iced.window.min_size = Some(min_size);
}
let max_size = settings.size_limits.max();
if max_size != iced::Size::INFINITY {
iced.window.max_size = Some(max_size);
}
iced.window.transparent = settings.transparent;
}
iced
} }
/// Launch a COSMIC application with the given [`Settings`]. /// Launch a COSMIC application with the given [`Settings`].
@ -158,9 +132,40 @@ pub(crate) fn iced_settings<App: Application>(
/// ///
/// Returns error on application failure. /// Returns error on application failure.
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result { pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
let settings = iced_settings::<App>(settings, flags); let default_font = settings.default_font;
let (settings, flags) = iced_settings::<App>(settings, flags);
cosmic::Cosmic::<App>::run(settings) #[cfg(not(feature = "multi-window"))]
{
iced::application(
cosmic::Cosmic::title,
cosmic::Cosmic::update,
cosmic::Cosmic::view,
)
.subscription(cosmic::Cosmic::subscription)
.style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme)
.window_size((500.0, 800.0))
.settings(settings)
.window(flags.2.clone())
.run_with(move || cosmic::Cosmic::<App>::init(flags))
}
#[cfg(feature = "multi-window")]
{
let mut app = multi_window::multi_window(
cosmic::Cosmic::title,
cosmic::Cosmic::update,
cosmic::Cosmic::view,
);
if flags.0.main_window.get().is_none() {
app = app.window(flags.2.clone());
_ = flags.0.main_window.set(iced_core::window::Id::RESERVED);
}
app.subscription(cosmic::Cosmic::subscription)
.style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme)
.settings(settings)
.run_with(move || cosmic::Cosmic::<App>::init(flags))
}
} }
#[cfg(feature = "single-instance")] #[cfg(feature = "single-instance")]
@ -362,9 +367,41 @@ where
tracing::info!("Another instance is running"); tracing::info!("Another instance is running");
Ok(()) Ok(())
} else { } else {
let mut settings = iced_settings::<App>(settings, flags); let (settings, mut flags) = iced_settings::<App>(settings, flags);
settings.flags.0.single_instance = true; flags.0.single_instance = true;
cosmic::Cosmic::<App>::run(settings)
#[cfg(not(feature = "multi-window"))]
{
iced::application(
cosmic::Cosmic::title,
cosmic::Cosmic::update,
cosmic::Cosmic::view,
)
.subscription(cosmic::Cosmic::subscription)
.style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme)
.window_size((500.0, 800.0))
.settings(settings)
.window(flags.2.clone())
.run_with(move || cosmic::Cosmic::<App>::init(flags))
}
#[cfg(feature = "multi-window")]
{
let mut app = multi_window::multi_window(
cosmic::Cosmic::title,
cosmic::Cosmic::update,
cosmic::Cosmic::view,
);
if flags.0.main_window.get().is_none() {
app = app.window(flags.2.clone());
_ = flags.0.main_window.set(iced_core::window::Id::RESERVED);
}
app.subscription(cosmic::Cosmic::subscription)
.style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme)
.settings(settings)
.run_with(move || cosmic::Cosmic::<App>::init(flags))
}
} }
} }
@ -409,7 +446,7 @@ where
fn core_mut(&mut self) -> &mut Core; fn core_mut(&mut self) -> &mut Core;
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>); fn init(core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>);
/// Displays a context drawer on the side of the application window when `Some`. /// Displays a context drawer on the side of the application window when `Some`.
fn context_drawer(&self) -> Option<Element<Self::Message>> { fn context_drawer(&self) -> Option<Element<Self::Message>> {
@ -441,11 +478,6 @@ where
Vec::new() Vec::new()
} }
/// Get the main [`window::Id`], which is [`window::Id::MAIN`] by default
fn main_window_id(&self) -> window::Id {
window::Id::MAIN
}
/// Allows overriding the default nav bar widget. /// Allows overriding the default nav bar widget.
fn nav_bar(&self) -> Option<Element<Message<Self::Message>>> { fn nav_bar(&self) -> Option<Element<Message<Self::Message>>> {
if !self.core().nav_bar_active() { if !self.core().nav_bar_active() {
@ -491,32 +523,32 @@ where
} }
// Called when context drawer is toggled // Called when context drawer is toggled
fn on_context_drawer(&mut self) -> Command<Self::Message> { fn on_context_drawer(&mut self) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Called when the escape key is pressed. /// Called when the escape key is pressed.
fn on_escape(&mut self) -> Command<Self::Message> { fn on_escape(&mut self) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Called when a navigation item is selected. /// Called when a navigation item is selected.
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Self::Message> { fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Called when a context menu is requested for a navigation item. /// Called when a context menu is requested for a navigation item.
fn on_nav_context(&mut self, id: nav_bar::Id) -> Command<Self::Message> { fn on_nav_context(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Called when the search function is requested. /// Called when the search function is requested.
fn on_search(&mut self) -> Command<Self::Message> { fn on_search(&mut self) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Called when a window is resized. /// Called when a window is resized.
fn on_window_resize(&mut self, id: window::Id, width: u32, height: u32) {} fn on_window_resize(&mut self, id: window::Id, width: f32, height: f32) {}
/// Event sources that are to be listened to. /// Event sources that are to be listened to.
fn subscription(&self) -> Subscription<Self::Message> { fn subscription(&self) -> Subscription<Self::Message> {
@ -524,8 +556,8 @@ where
} }
/// Respond to an application-specific message. /// Respond to an application-specific message.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Respond to a system theme change /// Respond to a system theme change
@ -533,8 +565,8 @@ where
&mut self, &mut self,
keys: &[&'static str], keys: &[&'static str],
new_theme: &cosmic_theme::Theme, new_theme: &cosmic_theme::Theme,
) -> Command<Self::Message> { ) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Respond to a system theme mode change /// Respond to a system theme mode change
@ -542,8 +574,8 @@ where
&mut self, &mut self,
keys: &[&'static str], keys: &[&'static str],
new_theme: &cosmic_theme::ThemeMode, new_theme: &cosmic_theme::ThemeMode,
) -> Command<Self::Message> { ) -> Task<Self::Message> {
Command::none() Task::none()
} }
/// Constructs the view for the main window. /// Constructs the view for the main window.
@ -555,33 +587,33 @@ where
} }
/// Overrides the default style for applications /// Overrides the default style for applications
fn style(&self) -> Option<<crate::Theme as iced_style::application::StyleSheet>::Style> { fn style(&self) -> Option<iced_runtime::Appearance> {
None None
} }
/// Handles dbus activation messages /// Handles dbus activation messages
#[cfg(feature = "single-instance")] #[cfg(feature = "single-instance")]
fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Command<Self::Message> { fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Task<Self::Message> {
Command::none() Task::none()
} }
} }
/// Methods automatically derived for all types implementing [`Application`]. /// Methods automatically derived for all types implementing [`Application`].
pub trait ApplicationExt: Application { pub trait ApplicationExt: Application {
/// Initiates a window drag. /// Initiates a window drag.
fn drag(&mut self) -> Command<Self::Message>; fn drag(&mut self) -> Task<Self::Message>;
/// Maximizes the window. /// Maximizes the window.
fn maximize(&mut self) -> Command<Self::Message>; fn maximize(&mut self) -> Task<Self::Message>;
/// Minimizes the window. /// Minimizes the window.
fn minimize(&mut self) -> Command<Self::Message>; fn minimize(&mut self) -> Task<Self::Message>;
/// Get the title of the main window. /// Get the title of the main window.
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn title(&self) -> &str; fn title(&self) -> &str;
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
/// Get the title of a window. /// Get the title of a window.
fn title(&self, id: window::Id) -> &str; fn title(&self, id: window::Id) -> &str;
@ -600,56 +632,58 @@ pub trait ApplicationExt: Application {
self.core_mut().set_header_title(title); self.core_mut().set_header_title(title);
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
/// Set the title of the main window. /// Set the title of the main window.
fn set_window_title(&mut self, title: String) -> Command<Self::Message>; fn set_window_title(&mut self, title: String) -> Task<Self::Message>;
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
/// Set the title of a window. /// Set the title of a window.
fn set_window_title(&mut self, title: String, id: window::Id) -> Command<Self::Message>; fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message>;
/// View template for the main window. /// View template for the main window.
fn view_main(&self) -> Element<Message<Self::Message>>; fn view_main(&self) -> Element<Message<Self::Message>>;
} }
impl<App: Application> ApplicationExt for App { impl<App: Application> ApplicationExt for App {
fn drag(&mut self) -> Command<Self::Message> { fn drag(&mut self) -> Task<Self::Message> {
command::drag(Some(self.main_window_id())) self.core().drag(None)
} }
fn maximize(&mut self) -> Command<Self::Message> { fn maximize(&mut self) -> Task<Self::Message> {
command::maximize(Some(self.main_window_id()), true) self.core().maximize(None, true)
} }
fn minimize(&mut self) -> Command<Self::Message> { fn minimize(&mut self) -> Task<Self::Message> {
command::minimize(Some(self.main_window_id())) self.core().minimize(None)
} }
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
fn title(&self, id: window::Id) -> &str { fn title(&self, id: window::Id) -> &str {
self.core().title.get(&id).map_or("", |s| s.as_str()) self.core().title.get(&id).map_or("", |s| s.as_str())
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn title(&self) -> &str { fn title(&self) -> &str {
self.core() self.core()
.title .main_window_id()
.get(&self.main_window_id()) .and_then(|id| self.core().title.get(&id).map(std::string::String::as_str))
.map_or("", |s| s.as_str()) .unwrap_or("")
} }
#[cfg(any(feature = "multi-window", feature = "wayland"))] #[cfg(feature = "multi-window")]
fn set_window_title(&mut self, title: String, id: window::Id) -> Command<Self::Message> { fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message> {
self.core_mut().title.insert(id, title.clone()); self.core_mut().title.insert(id, title.clone());
command::set_title(Some(id), title) self.core().set_title(Some(id), title)
} }
#[cfg(not(any(feature = "multi-window", feature = "wayland")))] #[cfg(not(feature = "multi-window"))]
fn set_window_title(&mut self, title: String) -> Command<Self::Message> { fn set_window_title(&mut self, title: String) -> Task<Self::Message> {
let id = self.main_window_id(); let Some(id) = self.core().main_window_id() else {
return Task::none();
};
self.core_mut().title.insert(id, title.clone()); self.core_mut().title.insert(id, title.clone());
Command::none() Task::none()
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
@ -659,7 +693,7 @@ impl<App: Application> ApplicationExt for App {
let is_condensed = core.is_condensed(); let is_condensed = core.is_condensed();
let focused = core let focused = core
.focused_window() .focused_window()
.is_some_and(|i| i == self.main_window_id()); .is_some_and(|i| Some(i) == self.core().main_window_id());
let content_row = crate::widget::row::with_children({ let content_row = crate::widget::row::with_children({
let mut widgets = Vec::with_capacity(4); let mut widgets = Vec::with_capacity(4);
@ -678,7 +712,7 @@ impl<App: Application> ApplicationExt for App {
if self.nav_model().is_none() || core.show_content() { if self.nav_model().is_none() || core.show_content() {
// Manual spacing must be used due to state workarounds below // Manual spacing must be used due to state workarounds below
if has_nav { if has_nav {
widgets.push(horizontal_space(Length::Fixed(8.0)).into()); widgets.push(horizontal_space().width(Length::Fixed(8.0)).into());
} }
let main_content = self.view().map(Message::App); let main_content = self.view().map(Message::App);
@ -727,7 +761,7 @@ impl<App: Application> ApplicationExt for App {
); );
} else { } else {
//TODO: this element is added to workaround state issues //TODO: this element is added to workaround state issues
widgets.push(horizontal_space(Length::Shrink).into()); widgets.push(horizontal_space().width(Length::Shrink).into());
} }
} }
} }
@ -744,7 +778,7 @@ impl<App: Application> ApplicationExt for App {
.padding([0, 8, 8, 8]) .padding([0, 8, 8, 8])
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Fill) .height(iced::Length::Fill)
.style(crate::theme::Container::WindowBackground) .class(crate::theme::Container::WindowBackground)
.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
.into() .into()
} else { } else {
@ -770,7 +804,7 @@ impl<App: Application> ApplicationExt for App {
} else { } else {
Message::Cosmic(cosmic::Message::ToggleNavBar) Message::Cosmic(cosmic::Message::ToggleNavBar)
}) })
.style(crate::theme::Button::HeaderBar); .class(crate::theme::Button::HeaderBar);
header = header.start(toggle); header = header.start(toggle);
} }
@ -825,11 +859,9 @@ impl<App: Application> ApplicationExt for App {
#[cfg(feature = "single-instance")] #[cfg(feature = "single-instance")]
fn single_instance_subscription<App: ApplicationExt>() -> Subscription<Message<App::Message>> { fn single_instance_subscription<App: ApplicationExt>() -> Subscription<Message<App::Message>> {
use iced_futures::futures::StreamExt; use iced_futures::futures::StreamExt;
iced_futures::Subscription::run_with_id(
iced::subscription::channel(
TypeId::of::<DbusActivation>(), TypeId::of::<DbusActivation>(),
10, iced::stream::channel(10, move |mut output| async move {
move |mut output| async move {
let mut single_instance: DbusActivation = DbusActivation::new(); let mut single_instance: DbusActivation = DbusActivation::new();
let mut rx = single_instance.rx(); let mut rx = single_instance.rx();
if let Ok(builder) = zbus::ConnectionBuilder::session() { if let Ok(builder) = zbus::ConnectionBuilder::session() {
@ -888,7 +920,7 @@ fn single_instance_subscription<App: ApplicationExt>() -> Subscription<Message<A
loop { loop {
iced::futures::pending!(); iced::futures::pending!();
} }
}, }),
) )
} }

238
src/app/multi_window.rs Normal file
View file

@ -0,0 +1,238 @@
//! Create and run daemons that run in the background.
//! Copied from iced 0.13, but adds optional initial window
use iced::application;
use iced::window;
use iced::{
self,
program::{self, with_style, with_subscription, with_theme, with_title},
runtime::{Appearance, DefaultStyle},
Program,
};
use iced::{Element, Result, Settings, Subscription, Task};
use std::marker::PhantomData;
pub(crate) struct Instance<State, Message, Theme, Renderer, Update, View> {
update: Update,
view: View,
_state: PhantomData<State>,
_message: PhantomData<Message>,
_theme: PhantomData<Theme>,
_renderer: PhantomData<Renderer>,
}
/// Creates an iced [`MultiWindow`] given its title, update, and view logic.
pub fn multi_window<State, Message, Theme, Renderer>(
title: impl Title<State>,
update: impl application::Update<State, Message>,
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
) -> MultiWindow<impl Program<State = State, Message = Message, Theme = Theme>>
where
State: 'static,
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Renderer: program::Renderer,
{
use std::marker::PhantomData;
impl<State, Message, Theme, Renderer, Update, View> Program
for Instance<State, Message, Theme, Renderer, Update, View>
where
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Renderer: program::Renderer,
Update: application::Update<State, Message>,
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
{
type State = State;
type Message = Message;
type Theme = Theme;
type Renderer = Renderer;
type Executor = iced_futures::backend::default::Executor;
fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
self.update.update(state, message).into()
}
fn view<'a>(
&self,
state: &'a Self::State,
window: window::Id,
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view.view(state, window).into()
}
}
MultiWindow {
raw: Instance {
update,
view,
_state: PhantomData,
_message: PhantomData,
_theme: PhantomData,
_renderer: PhantomData,
},
settings: Settings::default(),
window: None,
}
.title(title)
}
/// The underlying definition and configuration of an iced daemon.
///
/// You can use this API to create and run iced applications
/// step by step—without coupling your logic to a trait
/// or a specific type.
///
/// You can create a [`MultiWindow`] with the [`daemon`] helper.
#[derive(Debug)]
pub struct MultiWindow<P: Program> {
raw: P,
settings: Settings,
window: Option<window::Settings>,
}
impl<P: Program> MultiWindow<P> {
#[cfg(any(feature = "winit", feature = "wayland"))]
/// Runs the [`MultiWindow`].
///
/// The state of the [`MultiWindow`] must implement [`Default`].
/// If your state does not implement [`Default`], use [`run_with`]
/// instead.
///
/// [`run_with`]: Self::run_with
pub fn run(self) -> Result
where
Self: 'static,
P::State: Default,
{
self.raw.run(self.settings, self.window)
}
#[cfg(any(feature = "winit", feature = "wayland"))]
/// Runs the [`MultiWindow`] with a closure that creates the initial state.
pub fn run_with<I>(self, initialize: I) -> Result
where
Self: 'static,
I: FnOnce() -> (P::State, Task<P::Message>) + 'static,
{
self.raw.run_with(self.settings, self.window, initialize)
}
/// Sets the [`Settings`] that will be used to run the [`MultiWindow`].
pub fn settings(self, settings: Settings) -> Self {
Self { settings, ..self }
}
/// Sets the [`Title`] of the [`MultiWindow`].
pub(crate) fn title(
self,
title: impl Title<P::State>,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_title(self.raw, move |state, window| title.title(state, window)),
settings: self.settings,
window: self.window,
}
}
/// Sets the subscription logic of the [`MultiWindow`].
pub fn subscription(
self,
f: impl Fn(&P::State) -> Subscription<P::Message>,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_subscription(self.raw, f),
settings: self.settings,
window: self.window,
}
}
/// Sets the theme logic of the [`MultiWindow`].
pub fn theme(
self,
f: impl Fn(&P::State, window::Id) -> P::Theme,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_theme(self.raw, f),
settings: self.settings,
window: self.window,
}
}
/// Sets the style logic of the [`MultiWindow`].
pub fn style(
self,
f: impl Fn(&P::State, &P::Theme) -> Appearance,
) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
MultiWindow {
raw: with_style(self.raw, f),
settings: self.settings,
window: self.window,
}
}
/// Sets the window settings of the [`MultiWindow`].
pub fn window(self, window: window::Settings) -> Self {
Self {
raw: self.raw,
settings: self.settings,
window: Some(window),
}
}
}
/// The title logic of some [`MultiWindow`].
///
/// This trait is implemented both for `&static str` and
/// any closure `Fn(&State, window::Id) -> String`.
///
/// This trait allows the [`daemon`] builder to take any of them.
pub trait Title<State> {
/// Produces the title of the [`MultiWindow`].
fn title(&self, state: &State, window: window::Id) -> String;
}
impl<State> Title<State> for &'static str {
fn title(&self, _state: &State, _window: window::Id) -> String {
(*self).to_string()
}
}
impl<T, State> Title<State> for T
where
T: Fn(&State, window::Id) -> String,
{
fn title(&self, state: &State, window: window::Id) -> String {
self(state, window)
}
}
/// The view logic of some [`MultiWindow`].
///
/// This trait allows the [`daemon`] builder to take any closure that
/// returns any `Into<Element<'_, Message>>`.
pub trait View<'a, State, Message, Theme, Renderer> {
/// Produces the widget of the [`MultiWindow`].
fn view(
&self,
state: &'a State,
window: window::Id,
) -> impl Into<Element<'a, Message, Theme, Renderer>>;
}
impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T
where
T: Fn(&'a State, window::Id) -> Widget,
State: 'static,
Widget: Into<Element<'a, Message, Theme, Renderer>>,
{
fn view(
&self,
state: &'a State,
window: window::Id,
) -> impl Into<Element<'a, Message, Theme, Renderer>> {
self(state, window)
}
}

View file

@ -20,7 +20,6 @@ pub struct Settings {
pub(crate) autosize: bool, pub(crate) autosize: bool,
/// Set the application to not create a main window /// Set the application to not create a main window
#[cfg(feature = "wayland")]
pub(crate) no_main_window: bool, pub(crate) no_main_window: bool,
/// Whether the window should have a border, a title bar, etc. or not. /// Whether the window should have a border, a title bar, etc. or not.
@ -77,7 +76,6 @@ impl Default for Settings {
antialiasing: true, antialiasing: true,
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
autosize: false, autosize: false,
#[cfg(feature = "wayland")]
no_main_window: false, no_main_window: false,
client_decorations: true, client_decorations: true,
debug: false, debug: false,

View file

@ -2,7 +2,7 @@
pub mod token; pub mod token;
use crate::{ use crate::{
app::Core, app::{self, iced_settings, Core},
cctk::sctk, cctk::sctk,
iced::{ iced::{
self, self,
@ -10,9 +10,13 @@ use crate::{
widget::Container, widget::Container,
window, Color, Length, Limits, Rectangle, window, Color, Length, Limits, Rectangle,
}, },
iced_style, iced_widget, iced_widget,
theme::{self, system_dark, system_light, Button, THEME}, theme::{self, system_dark, system_light, Button, THEME},
widget::{self, layer_container}, widget::{
self,
autosize::{autosize, Autosize},
layer_container,
},
Application, Element, Renderer, Application, Element, Renderer,
}; };
use cctk::sctk::shell::xdg::window::WindowConfigure; use cctk::sctk::shell::xdg::window::WindowConfigure;
@ -21,14 +25,16 @@ use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
use cosmic_theme::Theme; use cosmic_theme::Theme;
use iced::Pixels; use iced::Pixels;
use iced_core::{Padding, Shadow}; use iced_core::{Padding, Shadow};
use iced_style::container::Appearance; use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
use iced_widget::runtime::command::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
use std::{borrow::Cow, num::NonZeroU32, rc::Rc}; use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock};
use tracing::info;
use crate::app::cosmic; use crate::app::cosmic;
static AUTOSIZE_ID: LazyLock<iced::id::Id> =
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize"));
static AUTOSIZE_MAIN_ID: LazyLock<iced::id::Id> =
LazyLock::new(|| iced::id::Id::new("cosmic-applet-autosize-main"));
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Context { pub struct Context {
@ -37,9 +43,9 @@ pub struct Context {
pub background: CosmicPanelBackground, pub background: CosmicPanelBackground,
pub output_name: String, pub output_name: String,
pub panel_type: PanelType, pub panel_type: PanelType,
/// Includes the suggested size of the window. /// Includes the configured size of the window.
/// This can be used by apples to handle overflow themselves. /// This can be used by apples to handle overflow themselves.
pub configure: Option<WindowConfigure>, pub suggested_bounds: Option<(iced::Size)>,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -94,7 +100,7 @@ impl Default for Context {
.unwrap_or(CosmicPanelBackground::ThemeDefault), .unwrap_or(CosmicPanelBackground::ThemeDefault),
output_name: std::env::var("COSMIC_PANEL_OUTPUT").unwrap_or_default(), output_name: std::env::var("COSMIC_PANEL_OUTPUT").unwrap_or_default(),
panel_type: PanelType::from(std::env::var("COSMIC_PANEL_NAME").unwrap_or_default()), panel_type: PanelType::from(std::env::var("COSMIC_PANEL_NAME").unwrap_or_default()),
configure: None, suggested_bounds: None,
} }
} }
} }
@ -116,19 +122,21 @@ impl Context {
let suggested = self.suggested_size(true); let suggested = self.suggested_size(true);
let applet_padding = self.suggested_padding(true); let applet_padding = self.suggested_padding(true);
let configured_width = self let configured_width = self
.configure .suggested_bounds
.as_ref() .as_ref()
.and_then(|c| c.new_size.0.map(|w| w)) .and_then(|c| NonZeroU32::new(c.width as u32)) // TODO: should this be physical size instead of logical?
.unwrap_or_else(|| { .unwrap_or_else(|| {
NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap() NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap()
}); });
let configured_height = self let configured_height = self
.configure .suggested_bounds
.as_ref() .as_ref()
.and_then(|c| c.new_size.1.map(|h| h)) .and_then(|c| NonZeroU32::new(c.height as u32))
.unwrap_or_else(|| { .unwrap_or_else(|| {
NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap() NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap()
}); });
info!("{configured_height:?}");
(configured_width, configured_height) (configured_width, configured_height)
} }
@ -149,18 +157,15 @@ impl Context {
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
pub fn window_settings(&self) -> crate::app::Settings { pub fn window_settings(&self) -> crate::app::Settings {
let (width, height) = self.suggested_size(true); let (width, height) = self.suggested_size(true);
let width = f32::from(width);
let height = f32::from(height);
let applet_padding = self.suggested_padding(true); let applet_padding = self.suggested_padding(true);
let width = f32::from(width) + applet_padding as f32 * 2.;
let height = f32::from(height) + applet_padding as f32 * 2.;
let mut settings = crate::app::Settings::default() let mut settings = crate::app::Settings::default()
.size(iced_core::Size::new( .size(iced_core::Size::new(width, height))
width + applet_padding as f32 * 2.,
height + applet_padding as f32 * 2.,
))
.size_limits( .size_limits(
Limits::NONE Limits::NONE
.min_height(height as f32 + applet_padding as f32 * 2.0) .min_height(height as f32)
.min_width(width as f32 + applet_padding as f32 * 2.0), .min_width(width as f32),
) )
.resizable(None) .resizable(None)
.default_text_size(14.0) .default_text_size(14.0)
@ -169,6 +174,7 @@ impl Context {
if let Some(theme) = self.theme() { if let Some(theme) = self.theme() {
settings = settings.theme(theme); settings = settings.theme(theme);
} }
settings.exit_on_close = true;
settings settings
} }
@ -182,26 +188,18 @@ impl Context {
&self, &self,
icon: widget::icon::Handle, icon: widget::icon::Handle,
) -> crate::widget::Button<'a, Message> { ) -> crate::widget::Button<'a, Message> {
let suggested = self.suggested_size(icon.symbolic); let mut suggested = self.suggested_size(icon.symbolic);
let applet_padding = self.suggested_padding(icon.symbolic); let applet_padding = self.suggested_padding(icon.symbolic);
let (mut configured_width, mut configured_height) = self.suggested_window_size();
// Adjust the width to include padding and force the crosswise dim to match the window size
let is_horizontal = self.is_horizontal(); let is_horizontal = self.is_horizontal();
if is_horizontal {
configured_width =
NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap();
} else {
configured_height =
NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap();
}
let symbolic = icon.symbolic; let symbolic = icon.symbolic;
crate::widget::button::custom( crate::widget::button::custom(
layer_container( layer_container(
widget::icon(icon) widget::icon(icon)
.style(if symbolic { .class(if symbolic {
theme::Svg::Custom(Rc::new(|theme| crate::iced_style::svg::Appearance { theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style {
color: Some(theme.cosmic().background.on.into()), color: Some(theme.cosmic().background.on.into()),
})) }))
} else { } else {
@ -215,9 +213,9 @@ impl Context {
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill), .height(Length::Fill),
) )
.width(Length::Fixed(configured_width.get() as f32)) .width(Length::Fixed((suggested.0 + 2 * applet_padding) as f32))
.height(Length::Fixed(configured_height.get() as f32)) .height(Length::Fixed((suggested.1 + 2 * applet_padding) as f32))
.style(Button::AppletIcon) .class(Button::AppletIcon)
} }
#[must_use] #[must_use]
@ -225,10 +223,11 @@ impl Context {
&self, &self,
icon_name: &'a str, icon_name: &'a str,
) -> crate::widget::Button<'a, Message> { ) -> crate::widget::Button<'a, Message> {
let suggested_size = self.suggested_size(true);
self.icon_button_from_handle( self.icon_button_from_handle(
widget::icon::from_name(icon_name) widget::icon::from_name(icon_name)
.symbolic(true) .symbolic(true)
.size(self.suggested_size(true).0) .size(suggested_size.0)
.into(), .into(),
) )
} }
@ -237,7 +236,7 @@ impl Context {
pub fn popup_container<'a, Message: 'static>( pub fn popup_container<'a, Message: 'static>(
&self, &self,
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message>>,
) -> Container<'a, Message, crate::Theme, Renderer> { ) -> Autosize<'a, Message, crate::Theme, Renderer> {
let (vertical_align, horizontal_align) = match self.anchor { let (vertical_align, horizontal_align) = match self.anchor {
PanelAnchor::Left => (Vertical::Center, Horizontal::Left), PanelAnchor::Left => (Vertical::Center, Horizontal::Left),
PanelAnchor::Right => (Vertical::Center, Horizontal::Right), PanelAnchor::Right => (Vertical::Center, Horizontal::Right),
@ -245,12 +244,12 @@ impl Context {
PanelAnchor::Bottom => (Vertical::Bottom, Horizontal::Center), PanelAnchor::Bottom => (Vertical::Bottom, Horizontal::Center),
}; };
Container::<Message, _, Renderer>::new( autosize(
Container::<Message, _, Renderer>::new(content).style(theme::Container::custom( Container::<Message, _, Renderer>::new(
|theme| { Container::<Message, _, Renderer>::new(content).style(|theme| {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let corners = cosmic.corner_radii.clone(); let corners = cosmic.corner_radii.clone();
Appearance { iced_widget::container::Style {
text_color: Some(cosmic.background.on.into()), text_color: Some(cosmic.background.on.into()),
background: Some(Color::from(cosmic.background.base).into()), background: Some(Color::from(cosmic.background.base).into()),
border: iced::Border { border: iced::Border {
@ -261,13 +260,21 @@ impl Context {
shadow: Shadow::default(), shadow: Shadow::default(),
icon_color: Some(cosmic.background.on.into()), icon_color: Some(cosmic.background.on.into()),
} }
}, }),
)), )
.width(Length::Shrink)
.height(Length::Shrink)
.align_x(horizontal_align)
.align_y(vertical_align),
AUTOSIZE_ID.clone(),
)
.limits(
Limits::NONE
.min_width(1.)
.min_height(1.)
.max_width(500.)
.max_height(1000.),
) )
.width(Length::Shrink)
.height(Length::Shrink)
.align_x(horizontal_align)
.align_y(vertical_align)
} }
#[must_use] #[must_use]
@ -282,7 +289,7 @@ impl Context {
) -> SctkPopupSettings { ) -> SctkPopupSettings {
let (width, height) = self.suggested_size(true); let (width, height) = self.suggested_size(true);
let applet_padding = self.suggested_padding(true); let applet_padding = self.suggested_padding(true);
let pixel_offset = 8; let pixel_offset = 4;
let (offset, anchor, gravity) = match self.anchor { let (offset, anchor, gravity) = match self.anchor {
PanelAnchor::Left => ((pixel_offset, 0), Anchor::Right, Gravity::Right), PanelAnchor::Left => ((pixel_offset, 0), Anchor::Right, Gravity::Right),
PanelAnchor::Right => ((-pixel_offset, 0), Anchor::Left, Gravity::Left), PanelAnchor::Right => ((-pixel_offset, 0), Anchor::Left, Gravity::Left),
@ -312,6 +319,35 @@ impl Context {
} }
} }
pub fn autosize_window<'a, Message: 'static>(
&self,
content: impl Into<Element<'a, Message>>,
) -> Autosize<'a, Message, crate::Theme, crate::Renderer> {
let force_configured = matches!(&self.panel_type, &PanelType::Other(ref n) if n.is_empty());
let w = autosize(content, AUTOSIZE_MAIN_ID.clone());
let mut limits = Limits::NONE;
let suggested_window_size = self.suggested_window_size();
if let Some(width) = self
.suggested_bounds
.as_ref()
.filter(|c| c.width as i32 > 0)
.map(|c| c.width)
{
limits = limits.width(width as f32);
}
if let Some(height) = self
.suggested_bounds
.as_ref()
.filter(|c| c.height as i32 > 0)
.map(|c| c.height)
{
limits = limits.height(height as f32);
}
w.limits(limits)
}
#[must_use] #[must_use]
pub fn theme(&self) -> Option<theme::Theme> { pub fn theme(&self) -> Option<theme::Theme> {
match self.background { match self.background {
@ -348,77 +384,67 @@ impl Context {
/// # Errors /// # Errors
/// ///
/// Returns error on application failure. /// Returns error on application failure.
pub fn run<App: Application>(autosize: bool, flags: App::Flags) -> iced::Result { pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
let helper = Context::default(); let helper = Context::default();
let mut settings = helper.window_settings();
settings.autosize = autosize;
if autosize {
settings.size_limits = Limits::NONE;
}
if let Some(icon_theme) = settings.default_icon_theme { let mut settings = helper.window_settings();
settings.resizable = None;
if let Some(icon_theme) = settings.default_icon_theme.clone() {
crate::icon_theme::set_default(icon_theme); crate::icon_theme::set_default(icon_theme);
} }
let (width, height) = (settings.size.width as u32, settings.size.height as u32); THEME
.lock()
.unwrap()
.set_theme(settings.theme.theme_type.clone());
let mut core = Core::default(); let (iced_settings, (mut core, flags, mut window_settings)) =
core.window.show_window_menu = false; iced_settings::<App>(settings, flags);
core.window.show_headerbar = false; core.window.show_headerbar = false;
core.window.sharp_corners = true; core.window.sharp_corners = true;
core.window.show_maximize = false; core.window.show_maximize = false;
core.window.show_minimize = false; core.window.show_minimize = false;
core.window.use_template = false; core.window.use_template = false;
core.debug = settings.debug; window_settings.decorations = false;
core.set_scale_factor(settings.scale_factor); window_settings.exit_on_close_request = true;
core.set_window_width(width); window_settings.resizable = false;
core.set_window_height(height); window_settings.resize_border = 0;
THEME.lock().unwrap().set_theme(settings.theme.theme_type); // TODO make multi-window not mandatory
let mut iced = iced::Settings::with_flags((core, flags)); let mut app = super::app::multi_window::multi_window(
cosmic::Cosmic::title,
iced.antialiasing = settings.antialiasing; cosmic::Cosmic::update,
iced.default_font = settings.default_font; cosmic::Cosmic::view,
iced.default_text_size = settings.default_text_size.into(); );
iced.id = Some(App::APP_ID.to_owned()); if core.main_window.get().is_none() {
app = app.window(window_settings.clone());
{ _ = core.main_window.set(iced_core::window::Id::RESERVED);
use iced::wayland::actions::window::SctkWindowSettings;
use iced_sctk::settings::InitialSurface;
iced.initial_surface = InitialSurface::XdgWindow(SctkWindowSettings {
app_id: Some(App::APP_ID.to_owned()),
autosize: settings.autosize,
client_decorations: settings.client_decorations,
resizable: settings.resizable,
size: (width, height),
size_limits: settings.size_limits,
title: None,
transparent: settings.transparent,
..SctkWindowSettings::default()
});
} }
app.subscription(cosmic::Cosmic::subscription)
<cosmic::Cosmic<App> as iced::Application>::run(iced) .style(cosmic::Cosmic::style)
.theme(cosmic::Cosmic::theme)
.settings(iced_settings)
.run_with(move || cosmic::Cosmic::<App>::init((core, flags, window_settings)))
} }
#[must_use] #[must_use]
pub fn style() -> <crate::Theme as iced_style::application::StyleSheet>::Style { pub fn style() -> iced_runtime::Appearance {
<crate::Theme as iced_style::application::StyleSheet>::Style::Custom(Box::new(|theme| { let theme = crate::theme::THEME.lock().unwrap();
iced_style::application::Appearance { iced_runtime::Appearance {
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0), background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
text_color: theme.cosmic().on_bg_color().into(), text_color: theme.cosmic().on_bg_color().into(),
icon_color: theme.cosmic().on_bg_color().into(), icon_color: theme.cosmic().on_bg_color().into(),
} }
}))
} }
pub fn menu_button<'a, Message>( pub fn menu_button<'a, Message>(
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message>>,
) -> crate::widget::Button<'a, Message> { ) -> crate::widget::Button<'a, Message> {
crate::widget::button::custom(content) crate::widget::button::custom(content)
.style(Button::AppletMenu) .class(Button::AppletMenu)
.padding(menu_control_padding()) .padding(menu_control_padding())
.width(Length::Fill) .width(Length::Fill)
} }

View file

@ -1,11 +1,12 @@
use crate::iced; use crate::iced;
use crate::iced::subscription;
use crate::iced_futures::futures; use crate::iced_futures::futures;
use cctk::sctk::reexports::calloop; use cctk::sctk::reexports::calloop;
use futures::{ use futures::{
channel::mpsc::{unbounded, UnboundedReceiver}, channel::mpsc::{unbounded, UnboundedReceiver},
SinkExt, StreamExt, SinkExt, StreamExt,
}; };
use iced::Subscription;
use iced_futures::stream;
use std::{fmt::Debug, hash::Hash, thread::JoinHandle}; use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
use super::wayland_handler::wayland_handler; use super::wayland_handler::wayland_handler;
@ -13,13 +14,16 @@ use super::wayland_handler::wayland_handler;
pub fn activation_token_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>( pub fn activation_token_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I, id: I,
) -> iced::Subscription<TokenUpdate> { ) -> iced::Subscription<TokenUpdate> {
subscription::channel(id, 50, move |mut output| async move { Subscription::run_with_id(
let mut state = State::Ready; id,
stream::channel(50, move |mut output| async move {
let mut state = State::Ready;
loop { loop {
state = start_listening(state, &mut output).await; state = start_listening(state, &mut output).await;
} }
}) }),
)
} }
pub enum State { pub enum State {

View file

@ -4,114 +4,55 @@
//! Create asynchronous actions to be performed in the background. //! Create asynchronous actions to be performed in the background.
use iced::window; use iced::window;
use iced::Command; use iced::Task;
use iced_core::window::Mode; use iced_core::window::Mode;
#[cfg(feature = "wayland")] use iced_runtime::{task, Action};
use iced_runtime::command::platform_specific::wayland::window::Action as WindowAction;
#[cfg(feature = "wayland")]
use iced_runtime::command::platform_specific::wayland::Action as WaylandAction;
#[cfg(feature = "wayland")]
use iced_runtime::command::platform_specific::Action as PlatformAction;
use iced_runtime::command::Action;
use std::future::Future; use std::future::Future;
/// Yields a command which contains a batch of commands. /// Yields a command which contains a batch of commands.
pub fn batch<X: 'static + Into<Y>, Y: 'static>( pub fn batch<X: Send + 'static + Into<Y>, Y: Send + 'static>(
commands: impl IntoIterator<Item = Command<X>>, commands: impl IntoIterator<Item = Task<X>>,
) -> Command<Y> { ) -> Task<Y> {
Command::batch(commands).map(Into::into) Task::batch(commands).map(Into::into)
} }
/// Yields a command which will run the future on the runtime executor. /// Yields a command which will run the future on the runtime executor.
pub fn future<X: Into<Y>, Y>(future: impl Future<Output = X> + Send + 'static) -> Command<Y> { pub fn future<X: Into<Y>, Y: 'static>(future: impl Future<Output = X> + Send + 'static) -> Task<Y> {
Command::single(Action::Future(Box::pin(async move { future.await.into() }))) Task::future(async move { future.await.into() })
} }
/// Yields a command which will return a message. /// Yields a command which will return a message.
pub fn message<X: Send + 'static + Into<Y>, Y>(message: X) -> Command<Y> { pub fn message<X: Send + 'static + Into<Y>, Y: 'static>(message: X) -> Task<Y> {
future(async move { message.into() }) future(async move { message.into() })
} }
/// Initiates a window drag. /// Initiates a window drag.
#[cfg(feature = "wayland")] pub fn drag<M>(id: window::Id) -> Task<M> {
pub fn drag<M>(id: Option<window::Id>) -> Command<M> { iced_runtime::window::drag(id)
iced_sctk::commands::window::start_drag_window(id.unwrap_or(window::Id::MAIN))
}
/// Initiates a window drag.
#[cfg(not(feature = "wayland"))]
pub fn drag<M>(id: Option<window::Id>) -> Command<M> {
iced_runtime::window::drag(id.unwrap_or(window::Id::MAIN))
} }
/// Maximizes the window. /// Maximizes the window.
#[cfg(feature = "wayland")] pub fn maximize<M>(id: window::Id, maximized: bool) -> Task<M> {
pub fn maximize<M>(id: Option<window::Id>, maximized: bool) -> Command<M> { iced_runtime::window::maximize(id, maximized)
iced_sctk::commands::window::maximize(id.unwrap_or(window::Id::MAIN), maximized)
}
/// Maximizes the window.
#[cfg(not(feature = "wayland"))]
pub fn maximize<M>(id: Option<window::Id>, maximized: bool) -> Command<M> {
iced_runtime::window::maximize(id.unwrap_or(window::Id::MAIN), maximized)
} }
/// Minimizes the window. /// Minimizes the window.
#[cfg(feature = "wayland")] pub fn minimize<M>(id: window::Id) -> Task<M> {
pub fn minimize<M>(id: Option<window::Id>) -> Command<M> { iced_runtime::window::minimize(id, true)
iced_sctk::commands::window::set_mode_window(id.unwrap_or(window::Id::MAIN), Mode::Hidden)
}
/// Minimizes the window.
#[cfg(not(feature = "wayland"))]
pub fn minimize<M>(id: Option<window::Id>) -> Command<M> {
iced_runtime::window::minimize(id.unwrap_or(window::Id::MAIN), true)
} }
/// Sets the title of a window. /// Sets the title of a window.
#[cfg(feature = "wayland")]
pub fn set_title<M>(id: Option<window::Id>, title: String) -> Command<M> {
window_action(WindowAction::Title {
id: id.unwrap_or(window::Id::MAIN),
title,
})
}
/// Sets the title of a window.
#[cfg(not(feature = "wayland"))]
#[allow(unused_variables, clippy::needless_pass_by_value)] #[allow(unused_variables, clippy::needless_pass_by_value)]
pub fn set_title<M>(id: Option<window::Id>, title: String) -> Command<M> { pub fn set_title<M>(id: window::Id, title: String) -> Task<M> {
Command::none() Task::none()
} }
/// Sets the window mode to windowed. /// Sets the window mode to windowed.
#[cfg(feature = "wayland")] pub fn set_windowed<M>(id: window::Id) -> Task<M> {
pub fn set_windowed<M>(id: Option<window::Id>) -> Command<M> { iced_runtime::window::change_mode(id, Mode::Windowed)
iced_sctk::commands::window::set_mode_window(id.unwrap_or(window::Id::MAIN), Mode::Windowed)
}
/// Sets the window mode to windowed.
#[cfg(not(feature = "wayland"))]
pub fn set_windowed<M>(id: Option<window::Id>) -> Command<M> {
iced_runtime::window::change_mode(id.unwrap_or(window::Id::MAIN), Mode::Windowed)
} }
/// Toggles the windows' maximize state. /// Toggles the windows' maximize state.
#[cfg(feature = "wayland")] pub fn toggle_maximize<M>(id: window::Id) -> Task<M> {
pub fn toggle_maximize<M>(id: Option<window::Id>) -> Command<M> { iced_runtime::window::toggle_maximize(id)
iced_sctk::commands::window::toggle_maximize(id.unwrap_or(window::Id::MAIN))
}
/// Toggles the windows' maximize state.
#[cfg(not(feature = "wayland"))]
pub fn toggle_maximize<M>(id: Option<window::Id>) -> Command<M> {
iced_runtime::window::toggle_maximize(id.unwrap_or(window::Id::MAIN))
}
/// Creates a command to apply an action to a window.
#[cfg(feature = "wayland")]
pub fn window_action<M>(action: WindowAction<M>) -> Command<M> {
Command::single(Action::PlatformSpecific(PlatformAction::Wayland(
WaylandAction::Window(action),
)))
} }

View file

@ -64,7 +64,7 @@ impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Column<'a
where where
E: Into<crate::Element<'a, Message>>, E: Into<crate::Element<'a, Message>>,
{ {
self.extend(other.drain(..)) self.extend(other.drain(..).map(Into::into))
} }
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self { fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self {
@ -77,7 +77,7 @@ impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Row<'a, M
where where
E: Into<crate::Element<'a, Message>>, E: Into<crate::Element<'a, Message>>,
{ {
self.extend(other.drain(..)) self.extend(other.drain(..).map(Into::into))
} }
fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self { fn push(self, element: impl Into<crate::Element<'a, Message>>) -> Self {

View file

@ -4,7 +4,7 @@
//! Select preferred fonts. //! Select preferred fonts.
pub use iced::Font; pub use iced::Font;
use iced_core::font::{Family, Weight}; use iced_core::font::Weight;
pub fn default() -> Font { pub fn default() -> Font {
Font::from(crate::config::interface_font()) Font::from(crate::config::interface_font())

View file

@ -17,7 +17,7 @@ pub enum Message {
} }
pub fn subscription() -> Subscription<Message> { pub fn subscription() -> Subscription<Message> {
listen_raw(|event, status| { listen_raw(|event, status, _| {
if event::Status::Ignored != status { if event::Status::Ignored != status {
return None; return None;
} }

View file

@ -4,28 +4,25 @@
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#![cfg_attr(target_os = "redox", feature(lazy_cell))] #![cfg_attr(target_os = "redox", feature(lazy_cell))]
#[cfg(all(feature = "wayland", feature = "winit"))]
compile_error!("cannot use `wayland` feature with `winit`");
/// Recommended default imports. /// Recommended default imports.
pub mod prelude { pub mod prelude {
pub use crate::ext::*; pub use crate::ext::*;
#[cfg(any(feature = "winit", feature = "wayland"))] #[cfg(feature = "winit")]
pub use crate::ApplicationExt; pub use crate::ApplicationExt;
pub use crate::{Also, Apply, Element, Renderer, Theme}; pub use crate::{Also, Apply, Element, Renderer, Theme};
} }
pub use apply::{Also, Apply}; pub use apply::{Also, Apply};
#[cfg(any(feature = "winit", feature = "wayland"))] #[cfg(feature = "winit")]
pub mod app; pub mod app;
#[cfg(any(feature = "winit", feature = "wayland"))] #[cfg(feature = "winit")]
pub use app::{Application, ApplicationExt}; pub use app::{Application, ApplicationExt};
#[cfg(feature = "applet")] #[cfg(feature = "applet")]
pub mod applet; pub mod applet;
pub use iced::Command; pub use iced::Task;
pub mod command; pub mod command;
pub mod config; pub mod config;
@ -62,12 +59,6 @@ pub use iced_renderer;
#[doc(inline)] #[doc(inline)]
pub use iced_runtime; pub use iced_runtime;
#[cfg(feature = "wayland")]
pub use iced_sctk;
#[doc(inline)]
pub use iced_style;
#[doc(inline)] #[doc(inline)]
pub use iced_widget; pub use iced_widget;
@ -96,7 +87,7 @@ pub mod theme;
pub use theme::{style, Theme}; pub use theme::{style, Theme};
pub mod widget; pub mod widget;
type Plain = iced_core::text::paragraph::Plain<<Renderer as iced_core::text::Renderer>::Paragraph>;
type Paragraph = <Renderer as iced_core::text::Renderer>::Paragraph; type Paragraph = <Renderer as iced_core::text::Renderer>::Paragraph;
pub type Renderer = iced::Renderer; pub type Renderer = iced::Renderer;
pub type Element<'a, Message> = iced::Element<'a, Message, crate::Theme, Renderer>; pub type Element<'a, Message> = iced::Element<'a, Message, crate::Theme, Renderer>;

View file

@ -14,6 +14,7 @@ use cosmic_config::CosmicConfigEntry;
use cosmic_theme::Component; use cosmic_theme::Component;
use cosmic_theme::LayeredTheme; use cosmic_theme::LayeredTheme;
use iced_futures::Subscription; use iced_futures::Subscription;
use iced_runtime::{Appearance, DefaultStyle};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -273,3 +274,14 @@ impl LayeredTheme for Theme {
self.layer = layer; self.layer = layer;
} }
} }
impl DefaultStyle for Theme {
fn default_style(&self) -> Appearance {
let cosmic = self.cosmic();
Appearance {
icon_color: cosmic.bg_color().into(),
background_color: cosmic.bg_color().into(),
text_color: cosmic.on_bg_color().into(),
}
}
}

View file

@ -1,7 +1,7 @@
use ashpd::desktop::settings::{ColorScheme, Contrast}; use ashpd::desktop::settings::{ColorScheme, Contrast};
use ashpd::desktop::Color; use ashpd::desktop::Color;
use iced::futures::{self, select, FutureExt, SinkExt, StreamExt}; use iced::futures::{self, select, FutureExt, SinkExt, StreamExt};
use iced_futures::subscription; use iced_futures::{stream, subscription};
use tracing::error; use tracing::error;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -12,86 +12,92 @@ pub enum Desktop {
} }
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> { pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
subscription::channel(std::any::TypeId::of::<Desktop>(), 10, |mut tx| { iced_futures::Subscription::run_with_id(
async move { std::any::TypeId::of::<Desktop>(),
let mut attempts = 0; stream::channel(10, |mut tx| {
loop { async move {
let Ok(settings) = ashpd::desktop::settings::Settings::new().await else { let mut attempts = 0;
error!("Failed to create the settings proxy");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts)))
.await;
#[cfg(not(feature = "tokio"))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
continue;
};
match settings.color_scheme().await {
Ok(color_scheme) => {
let _ = tx.send(Desktop::ColorScheme(color_scheme)).await;
}
Err(err) => error!("Failed to get the color scheme {err:?}"),
};
match settings.contrast().await {
Ok(contrast) => {
let _ = tx.send(Desktop::Contrast(contrast)).await;
}
Err(err) => error!("Failed to get the contrast {err:?}"),
};
let mut color_scheme_stream = settings.receive_color_scheme_changed().await.ok();
if color_scheme_stream.is_none() {
error!("Failed to receive color scheme changes");
}
let mut contrast_stream = settings.receive_contrast_changed().await.ok();
if contrast_stream.is_none() {
error!("Failed to receive contrast changes");
}
loop { loop {
if color_scheme_stream.is_none() && contrast_stream.is_none() { let Ok(settings) = ashpd::desktop::settings::Settings::new().await else {
break; error!("Failed to create the settings proxy");
#[cfg(feature = "tokio")]
::tokio::time::sleep(::tokio::time::Duration::from_secs(
2_u64.pow(attempts),
))
.await;
#[cfg(not(feature = "tokio"))]
{
pending::<()>().await;
unreachable!();
}
attempts += 1;
continue;
};
match settings.color_scheme().await {
Ok(color_scheme) => {
let _ = tx.send(Desktop::ColorScheme(color_scheme)).await;
}
Err(err) => error!("Failed to get the color scheme {err:?}"),
};
match settings.contrast().await {
Ok(contrast) => {
let _ = tx.send(Desktop::Contrast(contrast)).await;
}
Err(err) => error!("Failed to get the contrast {err:?}"),
};
let mut color_scheme_stream =
settings.receive_color_scheme_changed().await.ok();
if color_scheme_stream.is_none() {
error!("Failed to receive color scheme changes");
} }
let next_color_scheme = async {
if let Some(s) = color_scheme_stream.as_mut() {
return s.next().await;
}
futures::future::pending().await
};
let next_contrast = async { let mut contrast_stream = settings.receive_contrast_changed().await.ok();
if let Some(s) = contrast_stream.as_mut() { if contrast_stream.is_none() {
return s.next().await; error!("Failed to receive contrast changes");
} }
futures::future::pending().await
};
select! { loop {
s = next_color_scheme.fuse() => { if color_scheme_stream.is_none() && contrast_stream.is_none() {
if let Some(s) = s { break;
_ = tx.send(Desktop::ColorScheme(s)).await; }
} else { let next_color_scheme = async {
color_scheme_stream = None; if let Some(s) = color_scheme_stream.as_mut() {
return s.next().await;
} }
}, futures::future::pending().await
};
c = next_contrast.fuse() => { let next_contrast = async {
if let Some(c) = c { if let Some(s) = contrast_stream.as_mut() {
_ = tx.send(Desktop::Contrast(c)).await; return s.next().await;
} else {
contrast_stream = None;
} }
} futures::future::pending().await
}; };
// Reset the attempts counter if we successfully received a change
attempts = 0; select! {
s = next_color_scheme.fuse() => {
if let Some(s) = s {
_ = tx.send(Desktop::ColorScheme(s)).await;
} else {
color_scheme_stream = None;
}
},
c = next_contrast.fuse() => {
if let Some(c) = c {
_ = tx.send(Desktop::Contrast(c)).await;
} else {
contrast_stream = None;
}
}
};
// Reset the attempts counter if we successfully received a change
attempts = 0;
}
} }
} }
} }),
}) )
} }

View file

@ -8,17 +8,17 @@ use iced_core::{Background, Color};
use crate::{ use crate::{
theme::TRANSPARENT_COMPONENT, theme::TRANSPARENT_COMPONENT,
widget::button::{Appearance, StyleSheet}, widget::button::{Catalog, Style},
}; };
#[derive(Default)] #[derive(Default)]
pub enum Button { pub enum Button {
AppletIcon, AppletIcon,
Custom { Custom {
active: Box<dyn Fn(bool, &crate::Theme) -> Appearance>, active: Box<dyn Fn(bool, &crate::Theme) -> Style>,
disabled: Box<dyn Fn(&crate::Theme) -> Appearance>, disabled: Box<dyn Fn(&crate::Theme) -> Style>,
hovered: Box<dyn Fn(bool, &crate::Theme) -> Appearance>, hovered: Box<dyn Fn(bool, &crate::Theme) -> Style>,
pressed: Box<dyn Fn(bool, &crate::Theme) -> Appearance>, pressed: Box<dyn Fn(bool, &crate::Theme) -> Style>,
}, },
AppletMenu, AppletMenu,
Destructive, Destructive,
@ -44,10 +44,10 @@ pub fn appearance(
disabled: bool, disabled: bool,
style: &Button, style: &Button,
color: impl Fn(&Component) -> (Color, Option<Color>, Option<Color>), color: impl Fn(&Component) -> (Color, Option<Color>, Option<Color>),
) -> Appearance { ) -> Style {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let mut corner_radii = &cosmic.corner_radii.radius_xl; let mut corner_radii = &cosmic.corner_radii.radius_xl;
let mut appearance = Appearance::new(); let mut appearance = Style::new();
match style { match style {
Button::Standard Button::Standard
@ -163,10 +163,10 @@ pub fn appearance(
appearance appearance
} }
impl StyleSheet for crate::Theme { impl Catalog for crate::Theme {
type Style = Button; type Class = Button;
fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
if let Button::Custom { active, .. } = style { if let Button::Custom { active, .. } = style {
return active(focused, self); return active(focused, self);
} }
@ -186,7 +186,7 @@ impl StyleSheet for crate::Theme {
}) })
} }
fn disabled(&self, style: &Self::Style) -> Appearance { fn disabled(&self, style: &Self::Class) -> Style {
if let Button::Custom { disabled, .. } = style { if let Button::Custom { disabled, .. } = style {
return disabled(self); return disabled(self);
} }
@ -202,11 +202,11 @@ impl StyleSheet for crate::Theme {
}) })
} }
fn drop_target(&self, style: &Self::Style) -> Appearance { fn drop_target(&self, style: &Self::Class) -> Style {
self.active(false, false, style) self.active(false, false, style)
} }
fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
if let Button::Custom { hovered, .. } = style { if let Button::Custom { hovered, .. } = style {
return hovered(focused, self); return hovered(focused, self);
} }
@ -233,7 +233,7 @@ impl StyleSheet for crate::Theme {
) )
} }
fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
if let Button::Custom { pressed, .. } = style { if let Button::Custom { pressed, .. } = style {
return pressed(focused, self); return pressed(focused, self);
} }

File diff suppressed because it is too large Load diff

View file

@ -10,8 +10,6 @@ mod dropdown;
pub mod iced; pub mod iced;
#[doc(inline)] #[doc(inline)]
pub use self::iced::Application;
#[doc(inline)]
pub use self::iced::Checkbox; pub use self::iced::Checkbox;
#[doc(inline)] #[doc(inline)]
pub use self::iced::Container; pub use self::iced::Container;

View file

@ -9,9 +9,10 @@ use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::widget::Tree; use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget};
pub use iced_style::container::{Appearance, StyleSheet}; use iced_widget::container;
pub use iced_widget::container::{Catalog, Style};
pub fn aspect_ratio_container<'a, Message: 'static, T>( pub fn aspect_ratio_container<'a, Message: 'static, T>(
content: T, content: T,
@ -117,22 +118,22 @@ where
/// Centers the contents in the horizontal axis of the [`Container`]. /// Centers the contents in the horizontal axis of the [`Container`].
#[must_use] #[must_use]
pub fn center_x(mut self) -> Self { pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(); self.container = self.container.center_x(width);
self self
} }
/// Centers the contents in the vertical axis of the [`Container`]. /// Centers the contents in the vertical axis of the [`Container`].
#[must_use] #[must_use]
pub fn center_y(mut self) -> Self { pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(); self.container = self.container.center_y(height);
self self
} }
/// Sets the style of the [`Container`]. /// Sets the style of the [`Container`].
#[must_use] #[must_use]
pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self { pub fn class(mut self, style: impl Into<crate::style::Container<'a>>) -> Self {
self.container = self.container.style(style); self.container = self.container.class(style);
self self
} }
} }
@ -173,9 +174,7 @@ where
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
self.container.operate(tree, layout, renderer, operation); self.container.operate(tree, layout, renderer, operation);
} }
@ -241,8 +240,9 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer) self.container.overlay(tree, layout, renderer, translation)
} }
} }

276
src/widget/autosize.rs Normal file
View file

@ -0,0 +1,276 @@
//! Autosize Container, which will resize the window to its contents.
use cctk::sctk::shell::xdg::window;
use iced_core::event::{self, Event};
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Id, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style};
pub fn autosize<'a, Message: 'static, Theme, E>(
content: E,
id: Id,
) -> Autosize<'a, Message, Theme, crate::Renderer>
where
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
Theme: iced_widget::container::Catalog,
<Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
{
Autosize::new(content, id)
}
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Autosize<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
id: Id,
limits: layout::Limits,
auto_width: bool,
auto_height: bool,
}
impl<'a, Message, Theme, Renderer> Autosize<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer,
{
/// Creates an empty [`IdContainer`].
pub(crate) fn new<T>(content: T, id: Id) -> Self
where
T: Into<Element<'a, Message, Theme, Renderer>>,
{
Autosize {
content: content.into(),
id,
limits: layout::Limits::NONE,
auto_width: true,
auto_height: true,
}
}
pub fn limits(mut self, limits: layout::Limits) -> Self {
self.limits = limits;
self
}
pub fn auto_width(mut self, auto_width: bool) -> Self {
self.auto_width = auto_width;
self
}
pub fn auto_height(mut self, auto_height: bool) -> Self {
self.auto_height = auto_height;
self
}
pub fn max_width(mut self, v: f32) -> Self {
self.limits = self.limits.max_width(v);
self
}
pub fn max_height(mut self, v: f32) -> Self {
self.limits = self.limits.max_height(v);
self
}
pub fn min_width(mut self, v: f32) -> Self {
self.limits = self.limits.min_width(v);
self
}
pub fn min_height(mut self, v: f32) -> Self {
self.limits = self.limits.min_height(v);
self
}
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Autosize<'a, Message, Theme, Renderer>
where
Renderer: iced_core::Renderer,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn diff(&mut self, tree: &mut Tree) {
tree.children[0].diff(&mut self.content);
}
fn size(&self) -> iced_core::Size<Length> {
self.content.as_widget().size()
}
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
let mut limits = self.limits;
let min = self.limits.min();
let max = self.limits.max();
if self.auto_width {
limits.min_width(min.width);
limits.max_width(max.width);
}
if self.auto_height {
limits.min_height(min.height);
limits.max_height(max.height);
}
let node = self
.content
.as_widget()
.layout(&mut tree.children[0], renderer, &self.limits);
let size = node.size();
layout::Node::with_children(size, vec![node])
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
operation,
);
});
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
#[cfg(feature = "wayland")]
if matches!(
event,
Event::PlatformSpecific(event::PlatformSpecific::Wayland(
event::wayland::Event::RequestResize
))
) {
let bounds = layout.bounds().size();
clipboard.request_logical_window_size(bounds.width.max(1.), bounds.height.max(1.));
}
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event.clone(),
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
viewport,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().mouse_interaction(
&tree.children[0],
content_layout,
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
renderer_style,
content_layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
translation,
)
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
let content_layout = layout.children().next().unwrap();
self.content.as_widget().drag_destinations(
&state.children[0],
content_layout,
renderer,
dnd_rectangles,
);
}
fn id(&self) -> Option<crate::widget::Id> {
Some(self.id.clone())
}
fn set_id(&mut self, id: crate::widget::Id) {
self.id = id;
}
}
impl<'a, Message, Theme, Renderer> From<Autosize<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer: 'a + iced_core::Renderer,
Theme: 'a,
{
fn from(c: Autosize<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
Element::new(c)
}
}

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use super::{Builder, Style}; use super::{Builder, ButtonClass};
use crate::widget::{ use crate::widget::{
icon::{self, Handle}, icon::{self, Handle},
tooltip, tooltip,
@ -48,7 +48,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20, line_height: 20,
font_size: 14, font_size: 14,
font_weight: Weight::Normal, font_weight: Weight::Normal,
style: Style::Icon, class: ButtonClass::Icon,
variant: icon, variant: icon,
} }
} }
@ -121,7 +121,7 @@ impl<'a, Message> Button<'a, Message> {
pub fn vertical(mut self, vertical: bool) -> Self { pub fn vertical(mut self, vertical: bool) -> Self {
self.variant.vertical = vertical; self.variant.vertical = vertical;
self.style = Style::IconVertical; self.class = ButtonClass::IconVertical;
self self
} }
} }
@ -157,7 +157,7 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
crate::widget::column::with_children(content) crate::widget::column::with_children(content)
.padding(builder.padding) .padding(builder.padding)
.spacing(builder.spacing) .spacing(builder.spacing)
.align_items(Alignment::Center) .align_x(Alignment::Center)
.apply(super::custom) .apply(super::custom)
} else { } else {
crate::widget::row::with_children(content) crate::widget::row::with_children(content)
@ -165,7 +165,7 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.width(builder.width) .width(builder.width)
.height(builder.height) .height(builder.height)
.spacing(builder.spacing) .spacing(builder.spacing)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.apply(super::custom) .apply(super::custom)
}; };
@ -174,18 +174,22 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.id(builder.id) .id(builder.id)
.on_press_maybe(builder.on_press) .on_press_maybe(builder.on_press)
.selected(builder.variant.selected) .selected(builder.variant.selected)
.style(builder.style); .class(builder.class);
if builder.tooltip.is_empty() { if builder.tooltip.is_empty() {
button.into() button.into()
} else { } else {
tooltip(button, builder.tooltip, tooltip::Position::Top) tooltip(
.size(builder.font_size) button,
.font(crate::font::Font { crate::widget::text(builder.tooltip)
weight: builder.font_weight, .size(builder.font_size)
..crate::font::default() .font(crate::font::Font {
}) weight: builder.font_weight,
.into() ..crate::font::default()
}),
tooltip::Position::Top,
)
.into()
} }
} }
} }

View file

@ -42,7 +42,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20, line_height: 20,
font_size: 14, font_size: 14,
font_weight: Weight::Normal, font_weight: Weight::Normal,
style: Style::Image, class: crate::theme::style::Button::Image,
variant, variant,
} }
} }
@ -80,7 +80,7 @@ where
.selected(builder.variant.selected) .selected(builder.variant.selected)
.id(builder.id) .id(builder.id)
.on_press_maybe(builder.on_press) .on_press_maybe(builder.on_press)
.style(builder.style) .class(builder.class)
.into() .into()
} }
} }

View file

@ -4,7 +4,7 @@
//! Hyperlink button widget //! Hyperlink button widget
use super::Builder; use super::Builder;
use super::Style; use super::ButtonClass;
use crate::prelude::*; use crate::prelude::*;
use crate::widget::icon::{self, Handle}; use crate::widget::icon::{self, Handle};
use crate::widget::{button, row, tooltip}; use crate::widget::{button, row, tooltip};
@ -44,7 +44,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20, line_height: 20,
font_size: 14, font_size: 14,
font_weight: Weight::Normal, font_weight: Weight::Normal,
style: Style::Link, class: ButtonClass::Link,
variant: link, variant: link,
} }
} }
@ -81,23 +81,27 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.width(builder.width) .width(builder.width)
.height(builder.height) .height(builder.height)
.spacing(builder.spacing) .spacing(builder.spacing)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.apply(button::custom) .apply(button::custom)
.padding(0) .padding(0)
.id(builder.id) .id(builder.id)
.on_press_maybe(builder.on_press.take()) .on_press_maybe(builder.on_press.take())
.style(builder.style); .class(builder.class);
if builder.tooltip.is_empty() { if builder.tooltip.is_empty() {
button.into() button.into()
} else { } else {
tooltip(button, builder.tooltip, tooltip::Position::Top) tooltip(
.size(builder.font_size) button,
.font(crate::font::Font { crate::widget::text(builder.tooltip)
weight: builder.font_weight, .size(builder.font_size)
..crate::font::default() .font(crate::font::Font {
}) weight: builder.font_weight,
.into() ..crate::font::default()
}),
tooltip::Position::Top,
)
.into()
} }
} }
} }

View file

@ -3,7 +3,7 @@
//! Button widgets for COSMIC applications. //! Button widgets for COSMIC applications.
pub use crate::theme::Button as Style; pub use crate::theme::Button as ButtonClass;
pub mod link; pub mod link;
use derive_setters::Setters; use derive_setters::Setters;
@ -26,7 +26,7 @@ pub use image::Button as ImageButton;
mod style; mod style;
#[doc(inline)] #[doc(inline)]
pub use style::{Appearance, StyleSheet}; pub use style::{Catalog, Style};
mod text; mod text;
#[doc(inline)] #[doc(inline)]
@ -105,7 +105,7 @@ pub struct Builder<'a, Message, Variant> {
font_weight: Weight, font_weight: Weight,
/// The preferred style of the button. /// The preferred style of the button.
style: Style, class: ButtonClass,
#[setters(skip)] #[setters(skip)]
variant: Variant, variant: Variant,

View file

@ -9,7 +9,7 @@ use crate::theme::THEME;
/// The appearance of a button. /// The appearance of a button.
#[must_use] #[must_use]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Appearance { pub struct Style {
/// The amount of offset to apply to the shadow of the button. /// The amount of offset to apply to the shadow of the button.
pub shadow_offset: Vector, pub shadow_offset: Vector,
@ -41,7 +41,7 @@ pub struct Appearance {
pub text_color: Option<Color>, pub text_color: Option<Color>,
} }
impl Appearance { impl Style {
// TODO: `Radius` is not `const fn` compatible. // TODO: `Radius` is not `const fn` compatible.
pub fn new() -> Self { pub fn new() -> Self {
let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0; let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
@ -60,33 +60,34 @@ impl Appearance {
} }
} }
impl std::default::Default for Appearance { impl std::default::Default for Style {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
// TODO update to match other styles
/// A set of rules that dictate the style of a button. /// A set of rules that dictate the style of a button.
pub trait StyleSheet { pub trait Catalog {
/// The supported style of the [`StyleSheet`]. /// The supported style of the [`StyleSheet`].
type Style: Default; type Class: Default;
/// Produces the active [`Appearance`] of a button. /// Produces the active [`Appearance`] of a button.
fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; fn active(&self, focused: bool, selected: bool, style: &Self::Class) -> Style;
/// Produces the disabled [`Appearance`] of a button. /// Produces the disabled [`Appearance`] of a button.
fn disabled(&self, style: &Self::Style) -> Appearance; fn disabled(&self, style: &Self::Class) -> Style;
/// [`Appearance`] when the button is the target of a DND operation. /// [`Appearance`] when the button is the target of a DND operation.
fn drop_target(&self, style: &Self::Style) -> Appearance { fn drop_target(&self, style: &Self::Class) -> Style {
self.hovered(false, false, style) self.hovered(false, false, style)
} }
/// Produces the hovered [`Appearance`] of a button. /// Produces the hovered [`Appearance`] of a button.
fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; fn hovered(&self, focused: bool, selected: bool, style: &Self::Class) -> Style;
/// Produces the pressed [`Appearance`] of a button. /// Produces the pressed [`Appearance`] of a button.
fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style;
/// Background color of the selection indicator /// Background color of the selection indicator
fn selection_background(&self) -> Background; fn selection_background(&self) -> Background;

View file

@ -1,7 +1,7 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use super::{Builder, Style}; use super::{Builder, ButtonClass, Style};
use crate::widget::{icon, row, tooltip}; use crate::widget::{icon, row, tooltip};
use crate::{ext::CollectionWidget, Element}; use crate::{ext::CollectionWidget, Element};
use apply::Apply; use apply::Apply;
@ -14,14 +14,14 @@ pub type Button<'a, Message> = Builder<'a, Message, Text>;
pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> { pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new()) Button::new(Text::new())
.label(label) .label(label)
.style(Style::Destructive) .class(ButtonClass::Destructive)
} }
/// A text button with the suggested style /// A text button with the suggested style
pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> { pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new()) Button::new(Text::new())
.label(label) .label(label)
.style(Style::Suggested) .class(ButtonClass::Suggested)
} }
/// A text button with the standard style /// A text button with the standard style
@ -31,7 +31,9 @@ pub fn standard<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Messa
/// A text button with the text style /// A text button with the text style
pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> { pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
Button::new(Text::new()).label(label).style(Style::Text) Button::new(Text::new())
.label(label)
.class(ButtonClass::Text)
} }
/// The text variant of a button. /// The text variant of a button.
@ -66,7 +68,7 @@ impl<'a, Message> Button<'a, Message> {
line_height: 20, line_height: 20,
font_size: 14, font_size: 14,
font_weight: Weight::Normal, font_weight: Weight::Normal,
style: Style::Standard, class: ButtonClass::Standard,
variant: text, variant: text,
} }
} }
@ -125,23 +127,27 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
.width(builder.width) .width(builder.width)
.height(builder.height) .height(builder.height)
.spacing(builder.spacing) .spacing(builder.spacing)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.apply(super::custom) .apply(super::custom)
.padding(0) .padding(0)
.id(builder.id) .id(builder.id)
.on_press_maybe(builder.on_press.take()) .on_press_maybe(builder.on_press.take())
.style(builder.style); .class(builder.class);
if builder.tooltip.is_empty() { if builder.tooltip.is_empty() {
button.into() button.into()
} else { } else {
tooltip(button, builder.tooltip, tooltip::Position::Top) tooltip(
.size(builder.font_size) button,
.font(crate::font::Font { crate::widget::text(builder.tooltip)
weight: builder.font_weight, .size(builder.font_size)
..crate::font::default() .font(crate::font::Font {
}) weight: builder.font_weight,
.into() ..crate::font::default()
}),
tooltip::Position::Top,
)
.into()
} }
} }
} }

View file

@ -7,7 +7,7 @@
//! A [`Button`] has some local [`State`]. //! A [`Button`] has some local [`State`].
use iced_runtime::core::widget::Id; use iced_runtime::core::widget::Id;
use iced_runtime::{keyboard, Command}; use iced_runtime::{keyboard, task, Action, Task};
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::renderer::{self, Quad, Renderer}; use iced_core::renderer::{self, Quad, Renderer};
@ -20,11 +20,11 @@ use iced_core::{overlay, Shadow};
use iced_core::{ use iced_core::{
Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget,
}; };
use iced_renderer::core::widget::{operation, OperationOutputWrapper}; use iced_renderer::core::widget::operation;
use crate::theme::THEME; use crate::theme::THEME;
pub use super::style::{Appearance, StyleSheet}; pub use super::style::{Catalog, Style};
/// Internally defines different button widget variants. /// Internally defines different button widget variants.
enum Variant<Message> { enum Variant<Message> {
@ -173,7 +173,7 @@ impl<'a, Message> Button<'a, Message> {
} }
/// Sets the style variant of this [`Button`]. /// Sets the style variant of this [`Button`].
pub fn style(mut self, style: crate::theme::Button) -> Self { pub fn class(mut self, style: crate::theme::Button) -> Self {
self.style = style; self.style = style;
self self
} }
@ -257,7 +257,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
operation.container(None, layout.bounds(), &mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
self.content.as_widget().operate( self.content.as_widget().operate(
@ -470,10 +470,11 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
selection_background, selection_background,
); );
iced_core::svg::Renderer::draw( let svg_handle = svg::Svg::new(crate::widget::common::object_select().clone())
.color(icon_color);
iced_core::svg::Renderer::draw_svg(
renderer, renderer,
crate::widget::common::object_select().clone(), svg_handle,
Some(icon_color),
Rectangle { Rectangle {
width: 16.0, width: 16.0,
height: 16.0, height: 16.0,
@ -498,11 +499,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
}, },
selection_background, selection_background,
); );
let svg_handle = svg::Svg::new(close_icon.clone()).color(icon_color);
iced_core::svg::Renderer::draw( iced_core::svg::Renderer::draw_svg(
renderer, renderer,
close_icon.clone(), svg_handle,
Some(icon_color),
Rectangle { Rectangle {
width: 16.0, width: 16.0,
height: 16.0, height: 16.0,
@ -533,11 +533,16 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
mut translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let mut position = layout.bounds().position();
translation.x += position.x;
translation.y += position.y;
self.content.as_widget_mut().overlay( self.content.as_widget_mut().overlay(
&mut tree.children[0], &mut tree.children[0],
layout.children().next().unwrap(), layout.children().next().unwrap(),
renderer, renderer,
translation,
) )
} }
@ -748,11 +753,11 @@ pub fn update<'a, Message: Clone>(
pub fn draw<Renderer: iced_core::Renderer, Theme>( pub fn draw<Renderer: iced_core::Renderer, Theme>(
renderer: &mut Renderer, renderer: &mut Renderer,
bounds: Rectangle, bounds: Rectangle,
styling: &super::style::Appearance, styling: &super::style::Style,
draw_contents: impl FnOnce(&mut Renderer, &Appearance), draw_contents: impl FnOnce(&mut Renderer, &Style),
is_image: bool, is_image: bool,
) where ) where
Theme: super::style::StyleSheet, Theme: super::style::Catalog,
{ {
let doubled_border_width = styling.border_width * 2.0; let doubled_border_width = styling.border_width * 2.0;
let doubled_outline_width = styling.outline_width * 2.0; let doubled_outline_width = styling.outline_width * 2.0;
@ -909,9 +914,9 @@ pub fn mouse_interaction(
} }
} }
/// Produces a [`Command`] that focuses the [`Button`] with the given [`Id`]. /// Produces a [`Task`] that focuses the [`Button`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Command<Message> { pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
Command::widget(operation::focusable::focus(id)) task::effect(Action::Widget(Box::new(operation::focusable::focus(id))))
} }
impl operation::Focusable for State { impl operation::Focusable for State {

View file

@ -86,7 +86,7 @@ where
text(first_day_of_week.to_string()) text(first_day_of_week.to_string())
.size(12) .size(12)
.width(Length::Fixed(36.0)) .width(Length::Fixed(36.0))
.horizontal_alignment(Horizontal::Center), .align_x(Horizontal::Center),
); );
first_day_of_week = first_day_of_week.succ(); first_day_of_week = first_day_of_week.succ();
@ -138,17 +138,17 @@ fn date_button<Message>(
on_select: &dyn Fn(NaiveDate) -> Message, on_select: &dyn Fn(NaiveDate) -> Message,
) -> crate::widget::Button<'static, Message> { ) -> crate::widget::Button<'static, Message> {
let style = if is_day { let style = if is_day {
button::Style::Suggested button::ButtonClass::Suggested
} else { } else {
button::Style::Text button::ButtonClass::Text
}; };
let button = button::custom( let button = button::custom(
text(format!("{}", date.day())) text(format!("{}", date.day()))
.horizontal_alignment(Horizontal::Center) .align_x(Horizontal::Center)
.vertical_alignment(Vertical::Center), .align_y(Vertical::Center),
) )
.style(style) .class(style)
.height(Length::Fixed(36.0)) .height(Length::Fixed(36.0))
.width(Length::Fixed(36.0)); .width(Length::Fixed(36.0));

View file

@ -5,12 +5,12 @@ use iced_core::{Background, Color};
/// Appearance of the cards. /// Appearance of the cards.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Appearance { pub struct Style {
pub card_1: Background, pub card_1: Background,
pub card_2: Background, pub card_2: Background,
} }
impl Default for Appearance { impl Default for Style {
fn default() -> Self { fn default() -> Self {
Self { Self {
card_1: Background::Color(Color::WHITE), card_1: Background::Color(Color::WHITE),
@ -20,7 +20,7 @@ impl Default for Appearance {
} }
/// Defines the [`Appearance`] of a cards. /// Defines the [`Appearance`] of a cards.
pub trait StyleSheet { pub trait Catalog {
/// The default [`Appearance`] of the cards. /// The default [`Appearance`] of the cards.
fn default(&self) -> Appearance; fn default(&self) -> Style;
} }

View file

@ -10,10 +10,10 @@ use std::time::{Duration, Instant};
use crate::theme::iced::Slider; use crate::theme::iced::Slider;
use crate::theme::{Button, THEME}; use crate::theme::{Button, THEME};
use crate::widget::{container, segmented_button::Entity, slider}; use crate::widget::{button::Catalog, container, segmented_button::Entity, slider};
use crate::Element; use crate::Element;
use derive_setters::Setters; use derive_setters::Setters;
use iced::Command; use iced::Task;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::gradient::{ColorStop, Linear}; use iced_core::gradient::{ColorStop, Linear};
use iced_core::renderer::Quad; use iced_core::renderer::Quad;
@ -23,12 +23,11 @@ use iced_core::{
Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget, Rectangle, Renderer, Shadow, Shell, Size, Vector, Widget,
}; };
use iced_style::slider::{HandleShape, RailBackground}; use iced_widget::slider::{HandleShape, RailBackground};
use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row}; use iced_widget::{canvas, column, horizontal_space, row, scrollable, vertical_space, Row};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use palette::{FromColor, RgbHue}; use palette::{FromColor, RgbHue};
use super::button::StyleSheet;
use super::divider::horizontal; use super::divider::horizontal;
use super::icon::{self, from_name}; use super::icon::{self, from_name};
use super::segmented_button::{self, SingleSelect}; use super::segmented_button::{self, SingleSelect};
@ -135,7 +134,7 @@ impl ColorPickerModel {
) )
} }
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Command<Message> { pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Task<Message> {
match update { match update {
ColorPickerUpdate::ActiveColor(c) => { ColorPickerUpdate::ActiveColor(c) => {
self.must_clear_cache.store(true, Ordering::SeqCst); self.must_clear_cache.store(true, Ordering::SeqCst);
@ -206,7 +205,7 @@ impl ColorPickerModel {
self.copied_at = None; self.copied_at = None;
} }
}; };
Command::none() Task::none()
} }
#[must_use] #[must_use]
@ -298,7 +297,7 @@ where
.width(self.width), .width(self.width),
// canvas with gradient for the current color // canvas with gradient for the current color
// still needs the canvas and the handle to be drawn on it // still needs the canvas and the handle to be drawn on it
container(vertical_space(self.height)) container(vertical_space().height(self.height))
.width(self.width) .width(self.width)
.height(self.height), .height(self.height),
slider( slider(
@ -310,16 +309,21 @@ where
on_update(ColorPickerUpdate::ActiveColor(new)) on_update(ColorPickerUpdate::ActiveColor(new))
} }
) )
.style(Slider::Custom { .class(Slider::Custom {
active: Rc::new(|t| { active: Rc::new(|t| {
let cosmic = t.cosmic(); let cosmic = t.cosmic();
let mut a = slider::StyleSheet::active(t, &Slider::default()); let mut a =
a.rail.colors = RailBackground::Gradient { slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), // a.rail.colors = RailBackground::Gradient {
auto_angle: true, // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
}; // auto_angle: true,
// };
a.rail.backgrounds = (
Background::Color(Color::TRANSPARENT),
Background::Color(Color::TRANSPARENT),
);
a.rail.width = 8.0; a.rail.width = 8.0;
a.handle.color = Color::TRANSPARENT; a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 }; a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into(); a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0; a.handle.border_width = 4.0;
@ -327,13 +331,15 @@ where
}), }),
hovered: Rc::new(|t| { hovered: Rc::new(|t| {
let cosmic = t.cosmic(); let cosmic = t.cosmic();
let mut a = slider::StyleSheet::active(t, &Slider::default()); let mut a =
a.rail.colors = RailBackground::Gradient { slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), // a.rail.colors = RailBackground::Gradient {
auto_angle: true, // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
}; // auto_angle: true,
// };
a.rail.backgrounds = (Color::TRANSPARENT.into(), Color::TRANSPARENT.into());
a.rail.width = 8.0; a.rail.width = 8.0;
a.handle.color = Color::TRANSPARENT; a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 }; a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into(); a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0; a.handle.border_width = 4.0;
@ -341,13 +347,22 @@ where
}), }),
dragging: Rc::new(|t| { dragging: Rc::new(|t| {
let cosmic = t.cosmic(); let cosmic = t.cosmic();
let mut a = slider::StyleSheet::active(t, &Slider::default()); let mut a =
a.rail.colors = RailBackground::Gradient { slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()), // a.rail.backgrounds = (
auto_angle: true, // RailBackground::Gradient(Gradient {
}; // gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
// auto_angle: true,
// }),
// RailBackground::Gradient {
// gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
// auto_angle: true,
// },
// );
a.rail.backgrounds =
(iced::Color::TRANSPARENT.into(), Color::TRANSPARENT.into());
a.rail.width = 8.0; a.rail.width = 8.0;
a.handle.color = Color::TRANSPARENT; a.handle.background = Color::TRANSPARENT.into();
a.handle.shape = HandleShape::Circle { radius: 8.0 }; a.handle.shape = HandleShape::Circle { radius: 8.0 };
a.handle.border_color = cosmic.palette.neutral_10.into(); a.handle.border_color = cosmic.palette.neutral_10.into();
a.handle.border_width = 4.0; a.handle.border_width = 4.0;
@ -373,7 +388,7 @@ where
from_name("edit-copy-symbolic").size(spacing.space_s).into(), from_name("edit-copy-symbolic").size(spacing.space_s).into(),
)) ))
.on_press(on_update(ColorPickerUpdate::Copied(Instant::now()))) .on_press(on_update(ColorPickerUpdate::Copied(Instant::now())))
.style(Button::Text); .class(Button::Text);
match self.copied_at.take() { match self.copied_at.take() {
Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => { Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => {
@ -381,13 +396,13 @@ where
} }
Some(_) => tooltip( Some(_) => tooltip(
button, button,
copied_to_clipboard_label, text(copied_to_clipboard_label),
iced_widget::tooltip::Position::Bottom, iced_widget::tooltip::Position::Bottom,
) )
.into(), .into(),
None => tooltip( None => tooltip(
button, button,
copy_to_clipboard_label, text(copy_to_clipboard_label),
iced_widget::tooltip::Position::Bottom, iced_widget::tooltip::Position::Bottom,
) )
.into(), .into(),
@ -431,7 +446,7 @@ where
) )
.width(self.width) .width(self.width)
.direction(iced_widget::scrollable::Direction::Horizontal( .direction(iced_widget::scrollable::Direction::Horizontal(
scrollable::Properties::new().alignment(scrollable::Alignment::End), scrollable::Scrollbar::new().anchor(scrollable::Anchor::End),
)) ))
}] }]
.spacing(spacing.space_xxs), .spacing(spacing.space_xxs),
@ -445,7 +460,7 @@ where
button::custom( button::custom(
text(reset_to_default) text(reset_to_default)
.width(self.width) .width(self.width)
.horizontal_alignment(iced_core::alignment::Horizontal::Center) .align_x(iced_core::alignment::Horizontal::Center)
) )
.width(self.width) .width(self.width)
.on_press(on_update(ColorPickerUpdate::Reset)) .on_press(on_update(ColorPickerUpdate::Reset))
@ -461,18 +476,18 @@ where
button::custom( button::custom(
text(cancel) text(cancel)
.width(self.width) .width(self.width)
.horizontal_alignment(iced_core::alignment::Horizontal::Center) .align_x(iced_core::alignment::Horizontal::Center)
) )
.width(self.width) .width(self.width)
.on_press(on_update(ColorPickerUpdate::Cancel)), .on_press(on_update(ColorPickerUpdate::Cancel)),
button::custom( button::custom(
text(save) text(save)
.width(self.width) .width(self.width)
.horizontal_alignment(iced_core::alignment::Horizontal::Center) .align_x(iced_core::alignment::Horizontal::Center)
) )
.width(self.width) .width(self.width)
.on_press(on_update(ColorPickerUpdate::AppliedColor)) .on_press(on_update(ColorPickerUpdate::AppliedColor))
.style(Button::Suggested) .class(Button::Suggested)
] ]
.spacing(spacing.space_xs) .spacing(spacing.space_xs)
.width(self.width), .width(self.width),
@ -589,7 +604,7 @@ where
let translation = Vector::new(canvas_layout.bounds().x, canvas_layout.bounds().y); let translation = Vector::new(canvas_layout.bounds().x, canvas_layout.bounds().y);
iced_core::Renderer::with_translation(renderer, translation, |renderer| { iced_core::Renderer::with_translation(renderer, translation, |renderer| {
canvas::Renderer::draw(renderer, vec![geo]); iced_renderer::geometry::Renderer::draw_geometry(renderer, geo);
}); });
let bounds = canvas_layout.bounds(); let bounds = canvas_layout.bounds();
@ -657,10 +672,11 @@ where
state: &'b mut Tree, state: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.inner self.inner
.as_widget_mut() .as_widget_mut()
.overlay(&mut state.children[0], layout, renderer) .overlay(&mut state.children[0], layout, renderer, translation)
} }
fn on_event( fn on_event(
@ -782,12 +798,12 @@ pub fn color_button<'a, Message: 'static>(
let spacing = THEME.lock().unwrap().cosmic().spacing; let spacing = THEME.lock().unwrap().cosmic().spacing;
button::custom(if color.is_some() { button::custom(if color.is_some() {
Element::from(vertical_space(Length::Fixed(f32::from(spacing.space_s)))) Element::from(vertical_space().height(Length::Fixed(f32::from(spacing.space_s))))
} else { } else {
Element::from(column![ Element::from(column![
vertical_space(Length::FillPortion(6)), vertical_space().height(Length::FillPortion(6)),
row![ row![
horizontal_space(Length::FillPortion(6)), horizontal_space().width(Length::FillPortion(6)),
Icon::from( Icon::from(
icon::from_name("list-add-symbolic") icon::from_name("list-add-symbolic")
.prefer_svg(true) .prefer_svg(true)
@ -797,17 +813,17 @@ pub fn color_button<'a, Message: 'static>(
.width(icon_portion) .width(icon_portion)
.height(Length::Fill) .height(Length::Fill)
.content_fit(iced_core::ContentFit::Contain), .content_fit(iced_core::ContentFit::Contain),
horizontal_space(Length::FillPortion(6)), horizontal_space().width(Length::FillPortion(6)),
] ]
.height(icon_portion) .height(icon_portion)
.width(Length::Fill), .width(Length::Fill),
vertical_space(Length::FillPortion(6)), vertical_space().height(Length::FillPortion(6)),
]) ])
}) })
.width(Length::Fixed(f32::from(spacing.space_s))) .width(Length::Fixed(f32::from(spacing.space_s)))
.height(Length::Fixed(f32::from(spacing.space_s))) .height(Length::Fixed(f32::from(spacing.space_s)))
.on_press_maybe(on_press) .on_press_maybe(on_press)
.style(crate::theme::Button::Custom { .class(crate::theme::Button::Custom {
active: Box::new(move |focused, theme| { active: Box::new(move |focused, theme| {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
@ -817,7 +833,7 @@ pub fn color_button<'a, Message: 'static>(
(0.0, Color::TRANSPARENT) (0.0, Color::TRANSPARENT)
}; };
let standard = theme.active(focused, false, &Button::Standard); let standard = theme.active(focused, false, &Button::Standard);
button::Appearance { button::Style {
shadow_offset: Vector::default(), shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background), background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(), border_radius: cosmic.radius_xs().into(),
@ -834,7 +850,7 @@ pub fn color_button<'a, Message: 'static>(
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let standard = theme.disabled(&Button::Standard); let standard = theme.disabled(&Button::Standard);
button::Appearance { button::Style {
shadow_offset: Vector::default(), shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background), background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(), border_radius: cosmic.radius_xs().into(),
@ -857,7 +873,7 @@ pub fn color_button<'a, Message: 'static>(
}; };
let standard = theme.hovered(focused, false, &Button::Standard); let standard = theme.hovered(focused, false, &Button::Standard);
button::Appearance { button::Style {
shadow_offset: Vector::default(), shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background), background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(), border_radius: cosmic.radius_xs().into(),
@ -880,7 +896,7 @@ pub fn color_button<'a, Message: 'static>(
}; };
let standard = theme.pressed(focused, false, &Button::Standard); let standard = theme.pressed(focused, false, &Button::Standard);
button::Appearance { button::Style {
shadow_offset: Vector::default(), shadow_offset: Vector::default(),
background: color.map(Background::from).or(standard.background), background: color.map(Background::from).or(standard.background),
border_radius: cosmic.radius_xs().into(), border_radius: cosmic.radius_xs().into(),

View file

@ -4,13 +4,14 @@
use crate::Element; use crate::Element;
use iced::advanced::layout::{self, Layout}; use iced::advanced::layout::{self, Layout};
use iced::advanced::widget::{self, Operation, OperationOutputWrapper}; use iced::advanced::widget::{self, Operation};
use iced::advanced::{overlay, renderer}; use iced::advanced::{overlay, renderer};
use iced::advanced::{Clipboard, Shell}; use iced::advanced::{Clipboard, Shell};
use iced::{event, mouse, Event, Point, Rectangle, Size}; use iced::{event, mouse, Event, Point, Rectangle, Size};
use iced_core::Renderer; use iced_core::Renderer;
pub(super) struct Overlay<'a, 'b, Message> { pub(super) struct Overlay<'a, 'b, Message> {
pub(crate) position: Point,
pub(super) content: &'b mut Element<'a, Message>, pub(super) content: &'b mut Element<'a, Message>,
pub(super) tree: &'b mut widget::Tree, pub(super) tree: &'b mut widget::Tree,
pub(super) width: f32, pub(super) width: f32,
@ -21,13 +22,8 @@ impl<'a, 'b, Message> overlay::Overlay<Message, crate::Theme, crate::Renderer>
where where
Message: Clone, Message: Clone,
{ {
fn layout( fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
&mut self, let position = self.position;
renderer: &crate::Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, bounds) let limits = layout::Limits::new(Size::ZERO, bounds)
.width(self.width) .width(self.width)
.height(bounds.height - 8.0 - position.y); .height(bounds.height - 8.0 - position.y);
@ -98,7 +94,7 @@ where
&mut self, &mut self,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget_mut() .as_widget_mut()
@ -122,8 +118,9 @@ where
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> {
let translation = iced::Vector::new(self.position.x, self.position.y);
self.content self.content
.as_widget_mut() .as_widget_mut()
.overlay(self.tree, layout, renderer) .overlay(self.tree, layout, renderer, translation)
} }
} }

View file

@ -13,11 +13,9 @@ use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree}; use iced_core::widget::{Operation, Tree};
use iced_core::{ use iced_core::{
layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Padding, layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Padding,
Rectangle, Shell, Widget, Rectangle, Shell, Vector, Widget,
}; };
use iced_renderer::core::widget::OperationOutputWrapper;
#[must_use] #[must_use]
pub struct ContextDrawer<'a, Message> { pub struct ContextDrawer<'a, Message> {
id: Option<iced_core::widget::Id>, id: Option<iced_core::widget::Id>,
@ -48,19 +46,19 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
text::heading(header) text::heading(header)
.width(Length::FillPortion(1)) .width(Length::FillPortion(1))
.height(Length::Fill) .height(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center) .align_x(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center), .align_y(alignment::Vertical::Center),
) )
.push( .push(
button::text("Close") button::text("Close")
.trailing_icon(icon::from_name("go-next-symbolic")) .trailing_icon(icon::from_name("go-next-symbolic"))
.on_press(on_close) .on_press(on_close)
.style(crate::theme::Button::Link) .class(crate::theme::Button::Link)
.apply(container) .apply(container)
.width(Length::FillPortion(1)) .width(Length::FillPortion(1))
.height(Length::Fill) .height(Length::Fill)
.align_x(alignment::Horizontal::Right) .align_x(alignment::Horizontal::Right)
.center_y(), .center_y(Length::Fill),
) )
// XXX must be done after pushing elements or it may be overwritten by size hints from contents // XXX must be done after pushing elements or it may be overwritten by size hints from contents
.height(Length::Fixed(80.0)) .height(Length::Fixed(80.0))
@ -84,7 +82,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
container( container(
LayerContainer::new(pane) LayerContainer::new(pane)
.layer(cosmic_theme::Layer::Primary) .layer(cosmic_theme::Layer::Primary)
.style(crate::style::Container::ContextDrawer) .class(crate::style::Container::ContextDrawer)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.max_width(max_width), .max_width(max_width),
@ -159,7 +157,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDraw
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget() .as_widget()
@ -232,17 +230,20 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDraw
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
translation: Vector,
) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> {
let bounds = layout.bounds(); let bounds = layout.bounds();
Some(iced_overlay::Element::new( let mut position = layout.position();
layout.position(), position.x += translation.x;
Box::new(Overlay { position.y += translation.y;
content: &mut self.drawer,
tree: &mut tree.children[1], Some(iced_overlay::Element::new(Box::new(Overlay {
width: bounds.width, content: &mut self.drawer,
}), tree: &mut tree.children[1],
)) width: bounds.width,
position,
})))
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]

View file

@ -8,7 +8,7 @@ use crate::widget::menu::{
}; };
use derive_setters::Setters; use derive_setters::Setters;
use iced::touch::Finger; use iced::touch::Finger;
use iced::Event; use iced::{Event, Vector};
use iced_core::widget::{tree, Tree, Widget}; use iced_core::widget::{tree, Tree, Widget};
use iced_core::{event, mouse, touch, Length, Point, Size}; use iced_core::{event, mouse, touch, Length, Point, Size};
use std::collections::HashSet; use std::collections::HashSet;
@ -144,9 +144,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &mut Tree, tree: &mut Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
self.content self.content
.as_widget() .as_widget()
@ -213,6 +211,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree, tree: &'b mut Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
_renderer: &crate::Renderer, _renderer: &crate::Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_ref::<LocalState>(); let state = tree.state.downcast_ref::<LocalState>();
@ -247,6 +246,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
root_bounds_list: vec![bounds], root_bounds_list: vec![bounds],
path_highlight: Some(PathHighlight::MenuActive), path_highlight: Some(PathHighlight::MenuActive),
style: &crate::theme::menu_bar::MenuBarStyle::Default, style: &crate::theme::menu_bar::MenuBarStyle::Default,
position: Point::new(translation.x, translation.y),
} }
.overlay(), .overlay(),
) )

View file

@ -72,11 +72,13 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
let mut content_col = widget::column::with_capacity(3 + dialog.controls.len() * 2); let mut content_col = widget::column::with_capacity(3 + dialog.controls.len() * 2);
content_col = content_col.push(widget::text::title3(dialog.title)); content_col = content_col.push(widget::text::title3(dialog.title));
if let Some(body) = dialog.body { if let Some(body) = dialog.body {
content_col = content_col.push(widget::vertical_space(Length::Fixed(space_xxs.into()))); content_col =
content_col.push(widget::vertical_space().height(Length::Fixed(space_xxs.into())));
content_col = content_col.push(widget::text::body(body)); content_col = content_col.push(widget::text::body(body));
} }
for control in dialog.controls { for control in dialog.controls {
content_col = content_col.push(widget::vertical_space(Length::Fixed(space_s.into()))); content_col =
content_col.push(widget::vertical_space().height(Length::Fixed(space_s.into())));
content_col = content_col.push(control); content_col = content_col.push(control);
} }
@ -90,7 +92,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
if let Some(button) = dialog.tertiary_action { if let Some(button) = dialog.tertiary_action {
button_row = button_row.push(button); button_row = button_row.push(button);
} }
button_row = button_row.push(widget::horizontal_space(Length::Fill)); button_row = button_row.push(widget::horizontal_space().width(Length::Fill));
if let Some(button) = dialog.secondary_action { if let Some(button) = dialog.secondary_action {
button_row = button_row.push(button); button_row = button_row.push(button);
} }
@ -103,7 +105,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
widget::column::with_children(vec![content_row.into(), button_row.into()]) widget::column::with_children(vec![content_row.into(), button_row.into()])
.spacing(space_l), .spacing(space_l),
) )
.style(style::Container::Dialog) .class(style::Container::Dialog)
.padding(space_m) .padding(space_m)
.width(Length::Fixed(570.0)), .width(Length::Fixed(570.0)),
) )

View file

@ -3,6 +3,8 @@ use std::{
sync::atomic::{AtomicU64, Ordering}, sync::atomic::{AtomicU64, Ordering},
}; };
use iced::Vector;
use crate::{ use crate::{
iced::{ iced::{
clipboard::{ clipboard::{
@ -280,9 +282,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
tree: &mut Tree, tree: &mut Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
self.container self.container
.as_widget() .as_widget()
@ -496,10 +496,11 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
tree: &'b mut Tree, tree: &'b mut Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.container self.container
.as_widget_mut() .as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer) .overlay(&mut tree.children[0], layout, renderer, translation)
} }
fn drag_destinations( fn drag_destinations(

View file

@ -1,69 +1,77 @@
use std::any::Any; use std::any::Any;
use iced_core::window;
use crate::{ use crate::{
iced::{ iced::{
clipboard::dnd::{DndAction, DndEvent, SourceEvent}, clipboard::dnd::{DndAction, DndEvent, SourceEvent},
event, mouse, overlay, Event, Length, Point, Rectangle, event, mouse, overlay, Event, Length, Point, Rectangle, Vector,
}, },
iced_core::{ iced_core::{
self, layout, renderer, self, layout, renderer,
widget::{tree, Tree}, widget::{tree, Tree},
Clipboard, Shell, Clipboard, Shell,
}, },
iced_style,
widget::{container, Id, Widget}, widget::{container, Id, Widget},
Element, Element,
}; };
pub fn dnd_source< pub fn dnd_source<
'a, 'a,
Message: 'static, Message: Clone + 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + Send + 'static, D: iced::clipboard::mime::AsMimeTypes + Send + 'static,
>( >(
child: impl Into<Element<'a, Message>>, child: impl Into<Element<'a, Message>>,
) -> DndSource<'a, Message, AppMessage, D> { ) -> DndSource<'a, Message, D> {
DndSource::new(child) DndSource::new(child)
} }
pub struct DndSource<'a, Message, AppMessage, D> { pub struct DndSource<'a, Message, D> {
id: Id, id: Id,
action: DndAction, action: DndAction,
container: Element<'a, Message>, container: Element<'a, Message>,
window: Option<window::Id>,
drag_content: Option<Box<dyn Fn() -> D>>, drag_content: Option<Box<dyn Fn() -> D>>,
drag_icon: Option<Box<dyn Fn() -> (Element<'static, AppMessage>, tree::State)>>, drag_icon: Option<Box<dyn Fn() -> (Element<'static, ()>, tree::State)>>,
on_start: Option<Message>,
on_cancelled: Option<Message>,
on_finish: Option<Message>,
drag_threshold: f32, drag_threshold: f32,
_phantom: std::marker::PhantomData<AppMessage>,
} }
impl< impl<
'a, 'a,
Message: 'static, Message: Clone + 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> DndSource<'a, Message, AppMessage, D> > DndSource<'a, Message, D>
{ {
pub fn new(child: impl Into<Element<'a, Message>>) -> Self { pub fn new(child: impl Into<Element<'a, Message>>) -> Self {
Self { Self {
id: Id::unique(), id: Id::unique(),
window: None,
action: DndAction::Copy | DndAction::Move, action: DndAction::Copy | DndAction::Move,
container: container(child).into(), container: container(child).into(),
drag_content: None, drag_content: None,
drag_icon: None, drag_icon: None,
drag_threshold: 8.0, drag_threshold: 8.0,
_phantom: std::marker::PhantomData, on_start: None,
on_cancelled: None,
on_finish: None,
} }
} }
pub fn with_id(child: impl Into<Element<'a, Message>>, id: Id) -> Self { pub fn with_id(child: impl Into<Element<'a, Message>>, id: Id) -> Self {
Self { Self {
id, id,
window: None,
action: DndAction::Copy | DndAction::Move, action: DndAction::Copy | DndAction::Move,
container: container(child).into(), container: container(child).into(),
drag_content: None, drag_content: None,
drag_icon: None, drag_icon: None,
drag_threshold: 8.0, drag_threshold: 8.0,
_phantom: std::marker::PhantomData, on_start: None,
on_cancelled: None,
on_finish: None,
} }
} }
@ -82,7 +90,7 @@ impl<
#[must_use] #[must_use]
pub fn drag_icon( pub fn drag_icon(
mut self, mut self,
f: impl Fn() -> (Element<'static, AppMessage>, tree::State) + 'static, f: impl Fn() -> (Element<'static, ()>, tree::State) + 'static,
) -> Self { ) -> Self {
self.drag_icon = Some(Box::new(f)); self.drag_icon = Some(Box::new(f));
self self
@ -98,10 +106,15 @@ impl<
let Some(content) = self.drag_content.as_ref().map(|f| f()) else { let Some(content) = self.drag_content.as_ref().map(|f| f()) else {
return; return;
}; };
iced_core::clipboard::start_dnd( iced_core::clipboard::start_dnd(
clipboard, clipboard,
false, false,
Some(iced_core::clipboard::DndSource::Widget(self.id.clone())), if let Some(window) = self.window.as_ref() {
Some(iced_core::clipboard::DndSource::Surface(window.clone()))
} else {
Some(iced_core::clipboard::DndSource::Widget(self.id.clone()))
},
self.drag_icon.as_ref().map(|f| { self.drag_icon.as_ref().map(|f| {
let (icon, state) = f(); let (icon, state) = f();
( (
@ -116,14 +129,33 @@ impl<
self.action, self.action,
); );
} }
pub fn on_start(mut self, on_start: Option<Message>) -> Self {
self.on_start = on_start;
self
}
pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self {
self.on_cancelled = on_cancelled;
self
}
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
self.on_finish = on_finish;
self
}
pub fn window(mut self, window: window::Id) -> Self {
self.window = Some(window);
self
}
} }
impl< impl<
'a, 'a,
Message: 'static, Message: Clone + 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> Widget<Message, crate::Theme, crate::Renderer> for DndSource<'a, Message, AppMessage, D> > Widget<Message, crate::Theme, crate::Renderer> for DndSource<'a, Message, D>
{ {
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.container)] vec![Tree::new(&self.container)]
@ -165,9 +197,7 @@ impl<
tree: &mut Tree, tree: &mut Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id)); operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id));
operation.container(Some(&self.id), layout.bounds(), &mut |operation| { operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
@ -210,7 +240,6 @@ impl<
} }
state.left_pressed_position = Some(position); state.left_pressed_position = Some(position);
// dbg!(&state, &self.id);
return event::Status::Captured; return event::Status::Captured;
} }
} }
@ -229,8 +258,10 @@ impl<
return ret; return ret;
} }
if let Some(left_pressed_position) = state.left_pressed_position { if let Some(left_pressed_position) = state.left_pressed_position {
// dbg!(&state);
if position.distance(left_pressed_position) > self.drag_threshold { if position.distance(left_pressed_position) > self.drag_threshold {
if let Some(on_start) = self.on_start.as_ref() {
shell.publish(on_start.clone())
}
self.start_dnd(clipboard, state.cached_bounds); self.start_dnd(clipboard, state.cached_bounds);
state.is_dragging = true; state.is_dragging = true;
state.left_pressed_position = None; state.left_pressed_position = None;
@ -249,8 +280,21 @@ impl<
} }
_ => return ret, _ => return ret,
}, },
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled | SourceEvent::Finished)) => { Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
if state.is_dragging { if state.is_dragging {
if let Some(m) = self.on_cancelled.as_ref() {
shell.publish(m.clone());
}
state.is_dragging = false;
return event::Status::Captured;
}
return ret;
}
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
if state.is_dragging {
if let Some(m) = self.on_finish.as_ref() {
shell.publish(m.clone());
}
state.is_dragging = false; state.is_dragging = false;
return event::Status::Captured; return event::Status::Captured;
} }
@ -308,10 +352,11 @@ impl<
tree: &'b mut Tree, tree: &'b mut Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
self.container self.container
.as_widget_mut() .as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer) .overlay(&mut tree.children[0], layout, renderer, translation)
} }
fn drag_destinations( fn drag_destinations(
@ -319,7 +364,7 @@ impl<
state: &Tree, state: &Tree,
layout: layout::Layout<'_>, layout: layout::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.container.as_widget().drag_destinations( self.container.as_widget().drag_destinations(
&state.children[0], &state.children[0],
@ -340,12 +385,11 @@ impl<
impl< impl<
'a, 'a,
Message: 'static, Message: Clone + 'static,
AppMessage: 'static,
D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static,
> From<DndSource<'a, Message, AppMessage, D>> for Element<'a, Message> > From<DndSource<'a, Message, D>> for Element<'a, Message>
{ {
fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> { fn from(e: DndSource<'a, Message, D>) -> Element<'a, Message> {
Element::new(e) Element::new(e)
} }
} }

View file

@ -97,7 +97,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
position: Point, position: Point,
target_height: f32, target_height: f32,
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> { ) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
overlay::Element::new(position, Box::new(Overlay::new(self, target_height))) overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
} }
} }
@ -129,10 +129,15 @@ struct Overlay<'a, Message> {
width: f32, width: f32,
target_height: f32, target_height: f32,
style: (), style: (),
position: Point,
} }
impl<'a, Message: 'a> Overlay<'a, Message> { impl<'a, Message: 'a> Overlay<'a, Message> {
pub fn new<S: AsRef<str>>(menu: Menu<'a, S, Message>, target_height: f32) -> Self { pub fn new<S: AsRef<str>>(
menu: Menu<'a, S, Message>,
target_height: f32,
position: Point,
) -> Self {
let Menu { let Menu {
state, state,
options, options,
@ -160,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
container = container container = container
.padding(padding) .padding(padding)
.style(crate::style::Container::Dropdown); .class(crate::style::Container::Dropdown);
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
@ -170,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
width, width,
target_height, target_height,
style, style,
position,
} }
} }
} }
@ -177,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
for Overlay<'a, Message> for Overlay<'a, Message>
{ {
fn layout( fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
&mut self, let position = self.position;
renderer: &crate::Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> layout::Node {
let space_below = bounds.height - (position.y + self.target_height); let space_below = bounds.height - (position.y + self.target_height);
let space_above = position.y; let space_above = position.y;
@ -447,10 +448,13 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
appearance.selected_background, appearance.selected_background,
); );
svg::Renderer::draw( let svg_handle =
iced_core::Svg::new(crate::widget::common::object_select().clone())
.color(appearance.selected_text_color)
.border_radius(appearance.border_radius);
svg::Renderer::draw_svg(
renderer, renderer,
crate::widget::common::object_select().clone(), svg_handle,
Some(appearance.selected_text_color),
Rectangle { Rectangle {
x: item_x + item_width - 16.0 - 8.0, x: item_x + item_width - 16.0 - 8.0,
y: bounds.y + (bounds.height / 2.0 - 8.0), y: bounds.y + (bounds.height / 2.0 - 8.0),
@ -494,7 +498,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
text::Renderer::fill_text( text::Renderer::fill_text(
renderer, renderer,
Text { Text {
content: option.as_ref(), content: option.as_ref().to_string(),
bounds: bounds.size(), bounds: bounds.size(),
size: Pixels(text_size), size: Pixels(text_size),
line_height: self.text_line_height, line_height: self.text_line_height,
@ -502,7 +506,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
bounds.position(), bounds.position(),
color, color,

View file

@ -97,7 +97,7 @@ where
position: Point, position: Point,
target_height: f32, target_height: f32,
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> { ) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
overlay::Element::new(position, Box::new(Overlay::new(self, target_height))) overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
} }
} }
@ -129,12 +129,14 @@ struct Overlay<'a, Message> {
width: f32, width: f32,
target_height: f32, target_height: f32,
style: (), style: (),
position: Point,
} }
impl<'a, Message: 'a> Overlay<'a, Message> { impl<'a, Message: 'a> Overlay<'a, Message> {
pub fn new<S: AsRef<str>, Item: Clone + PartialEq>( pub fn new<S: AsRef<str>, Item: Clone + PartialEq>(
menu: Menu<'a, S, Item, Message>, menu: Menu<'a, S, Item, Message>,
target_height: f32, target_height: f32,
position: Point,
) -> Self { ) -> Self {
let Menu { let Menu {
state, state,
@ -163,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
container = container container = container
.padding(padding) .padding(padding)
.style(crate::style::Container::Dropdown); .class(crate::style::Container::Dropdown);
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>); state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
@ -173,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
width, width,
target_height, target_height,
style, style,
position,
} }
} }
} }
@ -180,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer> impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
for Overlay<'a, Message> for Overlay<'a, Message>
{ {
fn layout( fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
&mut self, let position = self.position;
renderer: &crate::Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> layout::Node {
let space_below = bounds.height - (position.y + self.target_height); let space_below = bounds.height - (position.y + self.target_height);
let space_above = position.y; let space_above = position.y;
@ -537,10 +535,13 @@ where
appearance.selected_background, appearance.selected_background,
); );
svg::Renderer::draw( let svg_handle =
svg::Svg::new(crate::widget::common::object_select().clone())
.color(appearance.selected_text_color)
.border_radius(appearance.border_radius);
svg::Renderer::draw_svg(
renderer, renderer,
crate::widget::common::object_select().clone(), svg_handle,
Some(appearance.selected_text_color),
Rectangle { Rectangle {
x: item_x + item_width - 16.0 - 8.0, x: item_x + item_width - 16.0 - 8.0,
y: bounds.y + (bounds.height / 2.0 - 8.0), y: bounds.y + (bounds.height / 2.0 - 8.0),
@ -587,7 +588,7 @@ where
text::Renderer::fill_text( text::Renderer::fill_text(
renderer, renderer,
Text { Text {
content: option.as_ref(), content: option.as_ref().to_string(),
bounds: bounds.size(), bounds: bounds.size(),
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: self.text_line_height, line_height: self.text_line_height,
@ -595,7 +596,7 @@ where
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
bounds.position(), bounds.position(),
color, color,
@ -636,7 +637,7 @@ where
text::Renderer::fill_text( text::Renderer::fill_text(
renderer, renderer,
Text { Text {
content: description.as_ref(), content: description.as_ref().to_string(),
bounds: bounds.size(), bounds: bounds.size(),
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)), line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)),
@ -644,7 +645,7 @@ where
horizontal_alignment: alignment::Horizontal::Center, horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
bounds.position(), bounds.position(),
appearance.description_color, appearance.description_color,

View file

@ -9,7 +9,7 @@ pub mod menu;
pub use menu::Menu; pub use menu::Menu;
mod widget; mod widget;
pub use widget::{Appearance, Dropdown, StyleSheet}; pub use widget::{Catalog, Dropdown, Style};
pub fn dropdown<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>( pub fn dropdown<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>(
model: &'a Model<S, Item>, model: &'a Model<S, Item>,

View file

@ -9,10 +9,13 @@ use iced_core::event::{self, Event};
use iced_core::text::{self, Paragraph, Text}; use iced_core::text::{self, Paragraph, Text};
use iced_core::widget::tree::{self, Tree}; use iced_core::widget::tree::{self, Tree};
use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow}; use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow};
use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget}; use iced_core::{
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
use iced_widget::pick_list;
use std::ffi::OsStr; use std::ffi::OsStr;
pub use iced_widget::style::pick_list::{Appearance, StyleSheet}; pub use iced_widget::pick_list::{Catalog, Style};
/// A widget for selecting a single value from a list of selections. /// A widget for selecting a single value from a list of selections.
#[derive(Setters)] #[derive(Setters)]
@ -98,7 +101,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
for (_, item) in &list.options { for (_, item) in &list.options {
state state
.selections .selections
.push((item.clone(), crate::Paragraph::new())); .push((item.clone(), crate::Plain::default()));
} }
} }
} }
@ -182,6 +185,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_mut::<State<Item>>(); let state = tree.state.downcast_mut::<State<Item>>();
@ -216,8 +220,8 @@ pub struct State<Item: Clone + PartialEq + 'static> {
keyboard_modifiers: keyboard::Modifiers, keyboard_modifiers: keyboard::Modifiers,
is_open: bool, is_open: bool,
hovered_option: Option<Item>, hovered_option: Option<Item>,
selections: Vec<(Item, crate::Paragraph)>, selections: Vec<(Item, crate::Plain)>,
descriptions: Vec<crate::Paragraph>, descriptions: Vec<crate::Plain>,
} }
impl<Item: Clone + PartialEq + 'static> State<Item> { impl<Item: Clone + PartialEq + 'static> State<Item> {
@ -259,7 +263,7 @@ pub fn layout(
text_size: f32, text_size: f32,
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
font: Option<crate::font::Font>, font: Option<crate::font::Font>,
selection: Option<(&str, &mut crate::Paragraph)>, selection: Option<(&str, &mut crate::Plain)>,
) -> layout::Node { ) -> layout::Node {
use std::f32; use std::f32;
@ -267,7 +271,7 @@ pub fn layout(
let max_width = match width { let max_width = match width {
Length::Shrink => { Length::Shrink => {
let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 { let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 {
paragraph.update(Text { paragraph.update(Text {
content: label, content: label,
bounds: Size::new(f32::MAX, f32::MAX), bounds: Size::new(f32::MAX, f32::MAX),
@ -277,7 +281,7 @@ pub fn layout(
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
paragraph.min_width().round() paragraph.min_width().round()
}; };
@ -410,7 +414,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
) )
.width({ .width({
let measure = let measure =
|label: &str, paragraph: &mut crate::Paragraph, line_height: text::LineHeight| { |label: &str, paragraph: &mut crate::Plain, line_height: text::LineHeight| {
paragraph.update(Text { paragraph.update(Text {
content: label, content: label,
bounds: Size::new(f32::MAX, f32::MAX), bounds: Size::new(f32::MAX, f32::MAX),
@ -420,7 +424,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
paragraph.min_width().round() paragraph.min_width().round()
}; };
@ -433,7 +437,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
let paragraph = if state.descriptions.len() > desc_count { let paragraph = if state.descriptions.len() > desc_count {
&mut state.descriptions[desc_count] &mut state.descriptions[desc_count]
} else { } else {
state.descriptions.push(crate::Paragraph::new()); state.descriptions.push(crate::Plain::default());
state.descriptions.last_mut().unwrap() state.descriptions.last_mut().unwrap()
}; };
desc_count += 1; desc_count += 1;
@ -448,7 +452,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
None => { None => {
state state
.selections .selections
.push((item.clone(), crate::Paragraph::new())); .push((item.clone(), crate::Plain::default()));
state.selections.len() - 1 state.selections.len() - 1
} }
}; };
@ -499,9 +503,9 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
let is_mouse_over = cursor.is_over(bounds); let is_mouse_over = cursor.is_over(bounds);
let style = if is_mouse_over { let style = if is_mouse_over {
theme.hovered(&()) theme.style(&(), pick_list::Status::Hovered)
} else { } else {
theme.active(&()) theme.style(&(), pick_list::Status::Active)
}; };
iced_core::Renderer::fill_quad( iced_core::Renderer::fill_quad(
@ -515,10 +519,10 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
); );
if let Some(handle) = state.icon.clone() { if let Some(handle) = state.icon.clone() {
svg::Renderer::draw( let svg_handle = iced_core::Svg::new(handle).color(style.text_color);
svg::Renderer::draw_svg(
renderer, renderer,
handle, svg_handle,
Some(style.text_color),
Rectangle { Rectangle {
x: bounds.x + bounds.width - gap - 16.0, x: bounds.x + bounds.width - gap - 16.0,
y: bounds.center_y() - 8.0, y: bounds.center_y() - 8.0,
@ -541,7 +545,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
text::Renderer::fill_text( text::Renderer::fill_text(
renderer, renderer,
Text { Text {
content, content: content.to_string(),
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: text_line_height, line_height: text_line_height,
font, font,
@ -549,7 +553,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
bounds.position(), bounds.position(),
style.text_color, style.text_color,

View file

@ -5,16 +5,18 @@
use super::menu::{self, Menu}; use super::menu::{self, Menu};
use crate::widget::icon; use crate::widget::icon;
use derive_setters::Setters; use derive_setters::Setters;
use iced::Radians;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::text::{self, Paragraph, Text}; use iced_core::text::{self, Paragraph, Text};
use iced_core::widget::tree::{self, Tree}; use iced_core::widget::tree::{self, Tree};
use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow}; use iced_core::{alignment, keyboard, layout, mouse, overlay, renderer, svg, touch, Shadow};
use iced_core::{Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Widget}; use iced_core::{
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
use iced_widget::pick_list::{self, Catalog};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
pub use iced_widget::style::pick_list::{Appearance, StyleSheet};
/// A widget for selecting a single value from a list of selections. /// A widget for selecting a single value from a list of selections.
#[derive(Setters)] #[derive(Setters)]
pub struct Dropdown<'a, S: AsRef<str>, Message> { pub struct Dropdown<'a, S: AsRef<str>, Message> {
@ -62,6 +64,29 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
font: None, font: None,
} }
} }
fn update_paragraphs(&self, state: &mut tree::State) {
let state = state.downcast_mut::<State>();
state
.selections
.resize_with(self.selections.len(), crate::Plain::default);
for (i, selection) in self.selections.iter().enumerate() {
state.selections[i].update(Text {
content: selection.as_ref(),
bounds: Size::INFINITY,
// TODO use the renderer default size
size: iced::Pixels(self.text_size.unwrap_or(14.0)),
line_height: self.text_line_height,
font: self.font.unwrap_or(crate::font::default()),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
wrapping: text::Wrapping::default(),
});
}
}
} }
impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer> impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer>
@ -80,7 +105,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
state state
.selections .selections
.resize_with(self.selections.len(), crate::Paragraph::new); .resize_with(self.selections.len(), crate::Plain::default);
state.hashes.resize(self.selections.len(), 0); state.hashes.resize(self.selections.len(), 0);
for (i, selection) in self.selections.iter().enumerate() { for (i, selection) in self.selections.iter().enumerate() {
@ -103,7 +128,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
} }
} }
@ -202,6 +227,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
@ -237,8 +263,8 @@ pub struct State {
keyboard_modifiers: keyboard::Modifiers, keyboard_modifiers: keyboard::Modifiers,
is_open: bool, is_open: bool,
hovered_option: Option<usize>, hovered_option: Option<usize>,
selections: Vec<crate::Paragraph>,
hashes: Vec<u64>, hashes: Vec<u64>,
selections: Vec<crate::Plain>,
} }
impl State { impl State {
@ -280,7 +306,7 @@ pub fn layout(
text_size: f32, text_size: f32,
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
font: Option<crate::font::Font>, font: Option<crate::font::Font>,
selection: Option<(&str, &mut crate::Paragraph)>, selection: Option<(&str, &mut crate::Plain)>,
) -> layout::Node { ) -> layout::Node {
use std::f32; use std::f32;
@ -288,7 +314,7 @@ pub fn layout(
let max_width = match width { let max_width = match width {
Length::Shrink => { Length::Shrink => {
let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 { let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 {
paragraph.update(Text { paragraph.update(Text {
content: label, content: label,
bounds: Size::new(f32::MAX, f32::MAX), bounds: Size::new(f32::MAX, f32::MAX),
@ -298,7 +324,7 @@ pub fn layout(
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
paragraph.min_width().round() paragraph.min_width().round()
}; };
@ -430,14 +456,14 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
None, None,
) )
.width({ .width({
let measure = |_label: &str, selection_paragraph: &mut crate::Paragraph| -> f32 { let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
selection_paragraph.min_width().round() selection_paragraph.min_width().round()
}; };
selections selections
.iter() .iter()
.zip(state.selections.iter_mut()) .zip(state.selections.iter_mut())
.map(|(label, selection)| measure(label.as_ref(), selection)) .map(|(label, selection)| measure(label.as_ref(), selection.raw()))
.fold(0.0, |next, current| current.max(next)) .fold(0.0, |next, current| current.max(next))
+ gap + gap
+ 16.0 + 16.0
@ -477,9 +503,9 @@ pub fn draw<'a, S>(
let is_mouse_over = cursor.is_over(bounds); let is_mouse_over = cursor.is_over(bounds);
let style = if is_mouse_over { let style = if is_mouse_over {
theme.hovered(&()) theme.style(&(), pick_list::Status::Hovered)
} else { } else {
theme.active(&()) theme.style(&(), pick_list::Status::Active)
}; };
iced_core::Renderer::fill_quad( iced_core::Renderer::fill_quad(
@ -493,10 +519,11 @@ pub fn draw<'a, S>(
); );
if let Some(handle) = state.icon.clone() { if let Some(handle) = state.icon.clone() {
svg::Renderer::draw( let svg_handle = svg::Svg::new(handle).color(style.text_color);
svg::Renderer::draw_svg(
renderer, renderer,
handle, svg_handle,
Some(style.text_color),
Rectangle { Rectangle {
x: bounds.x + bounds.width - gap - 16.0, x: bounds.x + bounds.width - gap - 16.0,
y: bounds.center_y() - 8.0, y: bounds.center_y() - 8.0,
@ -517,7 +544,7 @@ pub fn draw<'a, S>(
text::Renderer::fill_text( text::Renderer::fill_text(
renderer, renderer,
Text { Text {
content, content: content.to_string(),
size: iced::Pixels(text_size), size: iced::Pixels(text_size),
line_height: text_line_height, line_height: text_line_height,
font, font,
@ -525,7 +552,7 @@ pub fn draw<'a, S>(
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
bounds.position(), bounds.position(),
style.text_color, style.text_color,

View file

@ -6,9 +6,9 @@ use derive_setters::Setters;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree}; use iced_core::widget::{Operation, Tree};
use iced_core::{ use iced_core::{
layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Widget, layout, mouse, overlay, renderer, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector,
Widget,
}; };
use iced_renderer::core::widget::OperationOutputWrapper;
/// Responsively generates rows and columns of widgets based on its dimmensions. /// Responsively generates rows and columns of widgets based on its dimmensions.
#[derive(Setters)] #[derive(Setters)]
@ -132,7 +132,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
operation.container(None, layout.bounds(), &mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
self.children self.children
@ -225,8 +225,9 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children(&mut self.children, tree, layout, renderer) overlay::from_children(&mut self.children, tree, layout, renderer, translation)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
@ -252,7 +253,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
for ((e, layout), state) in self for ((e, layout), state) in self
.children .children

View file

@ -7,9 +7,8 @@ use iced_core::event::{self, Event};
use iced_core::widget::{Operation, Tree}; use iced_core::widget::{Operation, Tree};
use iced_core::{ use iced_core::{
layout, mouse, overlay, renderer, Alignment, Clipboard, Layout, Length, Padding, Rectangle, layout, mouse, overlay, renderer, Alignment, Clipboard, Layout, Length, Padding, Rectangle,
Shell, Widget, Shell, Vector, Widget,
}; };
use iced_renderer::core::widget::OperationOutputWrapper;
/// Responsively generates rows and columns of widgets based on its dimmensions. /// Responsively generates rows and columns of widgets based on its dimmensions.
#[must_use] #[must_use]
@ -154,7 +153,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
operation.container(None, layout.bounds(), &mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
self.children self.children
@ -247,8 +246,9 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
overlay::from_children(&mut self.children, tree, layout, renderer) overlay::from_children(&mut self.children, tree, layout, renderer, translation)
} }
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
@ -274,7 +274,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
for ((e, layout), state) in self for ((e, layout), state) in self
.children .children

View file

@ -6,7 +6,7 @@ use crate::{ext::CollectionWidget, widget, Element};
use apply::Apply; use apply::Apply;
use derive_setters::Setters; use derive_setters::Setters;
use iced::Length; use iced::Length;
use iced_core::{widget::tree, Widget}; use iced_core::{widget::tree, Vector, Widget};
use std::borrow::Cow; use std::borrow::Cow;
#[must_use] #[must_use]
@ -221,9 +221,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &mut tree::Tree, state: &mut tree::Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
let child_tree = &mut state.children[0]; let child_tree = &mut state.children[0];
let child_layout = layout.children().next().unwrap(); let child_layout = layout.children().next().unwrap();
@ -237,12 +235,16 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &'b mut tree::Tree, state: &'b mut tree::Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let child_tree = &mut state.children[0]; let child_tree = &mut state.children[0];
let child_layout = layout.children().next().unwrap(); let child_layout = layout.children().next().unwrap();
self.header_bar_inner self.header_bar_inner.as_widget_mut().overlay(
.as_widget_mut() child_tree,
.overlay(child_tree, child_layout, renderer) child_layout,
renderer,
translation,
)
} }
fn drag_destinations( fn drag_destinations(
@ -250,7 +252,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
state: &tree::Tree, state: &tree::Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
if let Some((child_tree, child_layout)) = if let Some((child_tree, child_layout)) =
state.children.iter().zip(layout.children()).next() state.children.iter().zip(layout.children()).next()
@ -274,7 +276,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
let mut end = std::mem::take(&mut self.end); let mut end = std::mem::take(&mut self.end);
// Also packs the window controls at the very end. // Also packs the window controls at the very end.
end.push(widget::horizontal_space(Length::Fixed(12.0)).into()); end.push(widget::horizontal_space().width(Length::Fixed(12.0)).into());
end.push(self.window_controls()); end.push(self.window_controls());
let height = match self.density.unwrap_or_else(crate::config::header_size) { let height = match self.density.unwrap_or_else(crate::config::header_size) {
@ -288,7 +290,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
// If elements exist in the start region, append them here. // If elements exist in the start region, append them here.
.push( .push(
widget::row::with_children(start) widget::row::with_children(start)
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.apply(widget::container) .apply(widget::container)
.align_x(iced::alignment::Horizontal::Left) .align_x(iced::alignment::Horizontal::Left)
.width(Length::Shrink), .width(Length::Shrink),
@ -297,32 +299,32 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
// This will otherwise use the title as a widget if a title was defined. // This will otherwise use the title as a widget if a title was defined.
.push(if !center.is_empty() { .push(if !center.is_empty() {
widget::row::with_children(center) widget::row::with_children(center)
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.apply(widget::container) .apply(widget::container)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::alignment::Horizontal::Center)
.width(Length::Fill) .width(Length::Fill)
.into() .into()
} else if self.title.is_empty() { } else if self.title.is_empty() {
widget::horizontal_space(Length::Fill).into() widget::horizontal_space().width(Length::Fill).into()
} else { } else {
self.title_widget() self.title_widget()
}) })
.push( .push(
widget::row::with_children(end) widget::row::with_children(end)
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.apply(widget::container) .apply(widget::container)
.align_x(iced::alignment::Horizontal::Right) .align_x(iced::alignment::Horizontal::Right)
.width(Length::Shrink), .width(Length::Shrink),
) )
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.height(Length::Fixed(height)) .height(Length::Fixed(height))
.padding([0, 8]) .padding([0, 8])
.spacing(8) .spacing(8)
.apply(widget::container) .apply(widget::container)
.style(crate::theme::Container::HeaderBar { .class(crate::theme::Container::HeaderBar {
focused: self.focused, focused: self.focused,
}) })
.center_y() .center_y(Length::Shrink)
.apply(widget::mouse_area); .apply(widget::mouse_area);
// Assigns a message to emit when the headerbar is dragged. // Assigns a message to emit when the headerbar is dragged.
@ -350,10 +352,8 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
widget::text::heading(title) widget::text::heading(title)
.apply(widget::container) .apply(widget::container)
.center_x() .center_x(Length::Fill)
.center_y() .center_y(Length::Fill)
.width(Length::Fill)
.height(Length::Fill)
.into() .into()
} }
@ -380,7 +380,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
.padding(8) .padding(8)
}; };
icon.style(crate::theme::Button::HeaderBar) icon.class(crate::theme::Button::HeaderBar)
.selected(self.focused) .selected(self.focused)
.icon_size($size) .icon_size($size)
.on_press($on_press) .on_press($on_press)
@ -405,8 +405,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
) )
.spacing(8) .spacing(8)
.apply(widget::container) .apply(widget::container)
.height(Length::Fill) .center_y(Length::Fill)
.center_y()
.into() .into()
} }
} }

View file

@ -55,7 +55,10 @@ pub fn from_raster_bytes(
) -> Handle { ) -> Handle {
Handle { Handle {
symbolic: false, symbolic: false,
data: Data::Image(image::Handle::from_memory(bytes)), data: match bytes.into() {
Cow::Owned(b) => Data::Image(image::Handle::from_bytes(b)),
Cow::Borrowed(b) => Data::Image(image::Handle::from_bytes(b)),
},
} }
} }
@ -66,12 +69,14 @@ pub fn from_raster_pixels(
pixels: impl Into<Cow<'static, [u8]>> pixels: impl Into<Cow<'static, [u8]>>
+ std::convert::AsRef<[u8]> + std::convert::AsRef<[u8]>
+ std::marker::Send + std::marker::Send
+ std::marker::Sync + std::marker::Sync,
+ 'static,
) -> Handle { ) -> Handle {
Handle { Handle {
symbolic: false, symbolic: false,
data: Data::Image(image::Handle::from_pixels(width, height, pixels)), data: match pixels.into() {
Cow::Owned(pixels) => Data::Image(image::Handle::from_bytes(pixels)),
Cow::Borrowed(pixels) => Data::Image(image::Handle::from_bytes(pixels)),
},
} }
} }

View file

@ -24,7 +24,7 @@ pub fn icon(handle: Handle) -> Icon {
handle, handle,
height: None, height: None,
size: 16, size: 16,
style: crate::theme::Svg::default(), class: crate::theme::Svg::default(),
width: None, width: None,
} }
} }
@ -40,7 +40,7 @@ pub fn from_name(name: impl Into<Arc<str>>) -> Named {
pub struct Icon { pub struct Icon {
#[setters(skip)] #[setters(skip)]
handle: Handle, handle: Handle,
style: crate::theme::Svg, class: crate::theme::Svg,
pub(super) size: u16, pub(super) size: u16,
content_fit: ContentFit, content_fit: ContentFit,
#[setters(strip_option)] #[setters(strip_option)]
@ -86,7 +86,7 @@ impl Icon {
let from_svg = |handle| { let from_svg = |handle| {
Svg::<crate::Theme>::new(handle) Svg::<crate::Theme>::new(handle)
.style(self.style.clone()) .class(self.class.clone())
.width( .width(
self.width self.width
.unwrap_or_else(|| Length::Fixed(f32::from(self.size))), .unwrap_or_else(|| Length::Fixed(f32::from(self.size))),

View file

@ -4,8 +4,8 @@ use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::widget::{Id, Tree}; use iced_core::widget::{Id, Tree};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Widget}; use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_style::container::{Appearance, StyleSheet}; pub use iced_widget::container::{Catalog, Style};
pub fn id_container<'a, Message: 'static, Theme, E>( pub fn id_container<'a, Message: 'static, Theme, E>(
content: E, content: E,
@ -13,8 +13,8 @@ pub fn id_container<'a, Message: 'static, Theme, E>(
) -> IdContainer<'a, Message, Theme, crate::Renderer> ) -> IdContainer<'a, Message, Theme, crate::Renderer>
where where
E: Into<Element<'a, Message, Theme, crate::Renderer>>, E: Into<Element<'a, Message, Theme, crate::Renderer>>,
Theme: iced_style::container::StyleSheet, Theme: iced_widget::container::Catalog,
<Theme as iced_widget::container::StyleSheet>::Style: From<crate::theme::Container>, <Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
{ {
IdContainer::new(content, id) IdContainer::new(content, id)
} }
@ -83,9 +83,7 @@ where
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
operation.container(Some(&self.id), layout.bounds(), &mut |operation| { operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
self.content.as_widget().operate( self.content.as_widget().operate(
@ -165,11 +163,13 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay( self.content.as_widget_mut().overlay(
&mut tree.children[0], &mut tree.children[0],
layout.children().next().unwrap(), layout.children().next().unwrap(),
renderer, renderer,
translation,
) )
} }
@ -178,7 +178,7 @@ where
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
let content_layout = layout.children().next().unwrap(); let content_layout = layout.children().next().unwrap();
self.content.as_widget().drag_destinations( self.content.as_widget().drag_destinations(

View file

@ -1,3 +1,4 @@
use crate::Theme;
use cosmic_theme::LayeredTheme; use cosmic_theme::LayeredTheme;
use iced::widget::Container; use iced::widget::Container;
use iced_core::alignment; use iced_core::alignment;
@ -7,16 +8,14 @@ use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::widget::Tree; use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget};
pub use iced_style::container::{Appearance, StyleSheet}; pub use iced_widget::container::{Catalog, Style};
pub fn layer_container<'a, Message: 'static, Theme, E>( pub fn layer_container<'a, Message: 'static, E>(
content: E, content: E,
) -> LayerContainer<'a, Message, Theme, crate::Renderer> ) -> LayerContainer<'a, Message, crate::Renderer>
where where
E: Into<Element<'a, Message, Theme, crate::Renderer>>, E: Into<Element<'a, Message, Theme, crate::Renderer>>,
Theme: iced_style::container::StyleSheet + LayeredTheme,
<Theme as iced_widget::container::StyleSheet>::Style: From<crate::theme::Container>,
{ {
LayerContainer::new(content) LayerContainer::new(content)
} }
@ -25,20 +24,18 @@ where
/// ///
/// It is normally used for alignment purposes. /// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct LayerContainer<'a, Message, Theme, Renderer> pub struct LayerContainer<'a, Message, Renderer>
where where
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme,
{ {
layer: Option<cosmic_theme::Layer>, layer: Option<cosmic_theme::Layer>,
container: Container<'a, Message, Theme, Renderer>, container: Container<'a, Message, Theme, Renderer>,
} }
impl<'a, Message, Theme, Renderer> LayerContainer<'a, Message, Theme, Renderer> impl<'a, Message, Renderer> LayerContainer<'a, Message, Renderer>
where where
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme, // iced_widget::container::Style: From<crate::theme::Container>,
<Theme as iced_style::container::StyleSheet>::Style: From<crate::theme::Container>,
{ {
/// Creates an empty [`Container`]. /// Creates an empty [`Container`].
pub(crate) fn new<T>(content: T) -> Self pub(crate) fn new<T>(content: T) -> Self
@ -55,7 +52,7 @@ where
#[must_use] #[must_use]
pub fn layer(mut self, layer: cosmic_theme::Layer) -> Self { pub fn layer(mut self, layer: cosmic_theme::Layer) -> Self {
self.layer = Some(layer); self.layer = Some(layer);
self.style(match layer { self.class(match layer {
cosmic_theme::Layer::Background => crate::theme::Container::Background, cosmic_theme::Layer::Background => crate::theme::Container::Background,
cosmic_theme::Layer::Primary => crate::theme::Container::Primary, cosmic_theme::Layer::Primary => crate::theme::Container::Primary,
cosmic_theme::Layer::Secondary => crate::theme::Container::Secondary, cosmic_theme::Layer::Secondary => crate::theme::Container::Secondary,
@ -113,31 +110,30 @@ where
/// Centers the contents in the horizontal axis of the [`LayerContainer`]. /// Centers the contents in the horizontal axis of the [`LayerContainer`].
#[must_use] #[must_use]
pub fn center_x(mut self) -> Self { pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(); self.container = self.container.center_x(width);
self self
} }
/// Centers the contents in the vertical axis of the [`LayerContainer`]. /// Centers the contents in the vertical axis of the [`LayerContainer`].
#[must_use] #[must_use]
pub fn center_y(mut self) -> Self { pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(); self.container = self.container.center_y(height);
self self
} }
/// Sets the style of the [`LayerContainer`]. /// Sets the style of the [`LayerContainer`].
#[must_use] #[must_use]
pub fn style(mut self, style: impl Into<<Theme as StyleSheet>::Style>) -> Self { pub fn class(mut self, style: impl Into<crate::style::iced::Container<'a>>) -> Self {
self.container = self.container.style(style); self.container = self.container.class(style);
self self
} }
} }
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Renderer> Widget<Message, Theme, Renderer>
for LayerContainer<'a, Message, Theme, Renderer> for LayerContainer<'a, Message, Renderer>
where where
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme + Clone,
{ {
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
self.container.children() self.container.children()
@ -173,9 +169,7 @@ where
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
self.container.operate(tree, layout, renderer, operation); self.container.operate(tree, layout, renderer, operation);
} }
@ -232,6 +226,7 @@ where
} else { } else {
theme.clone() theme.clone()
}; };
self.container.draw( self.container.draw(
tree, tree,
renderer, renderer,
@ -248,8 +243,9 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.container.overlay(tree, layout, renderer) self.container.overlay(tree, layout, renderer, translation)
} }
fn drag_destinations( fn drag_destinations(
@ -257,7 +253,7 @@ where
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.container self.container
.drag_destinations(state, layout, renderer, dnd_rectangles); .drag_destinations(state, layout, renderer, dnd_rectangles);
@ -272,15 +268,14 @@ where
} }
} }
impl<'a, Message, Theme, Renderer> From<LayerContainer<'a, Message, Theme, Renderer>> impl<'a, Message, Renderer> From<LayerContainer<'a, Message, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Renderer: 'a + iced_core::Renderer, Renderer: 'a + iced_core::Renderer,
Theme: iced_style::container::StyleSheet + LayeredTheme + 'a + Clone,
{ {
fn from( fn from(
column: LayerContainer<'a, Message, Theme, Renderer>, column: LayerContainer<'a, Message, Renderer>,
) -> Element<'a, Message, Theme, Renderer> { ) -> Element<'a, Message, Theme, Renderer> {
Element::new(column) Element::new(column)
} }

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use iced_core::Padding; use iced_core::Padding;
use iced_style::container::StyleSheet; use iced_widget::container::Catalog;
use crate::{theme, widget::divider, Apply, Element}; use crate::{theme, widget::divider, Apply, Element};
@ -14,7 +14,7 @@ pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
pub struct ListColumn<'a, Message> { pub struct ListColumn<'a, Message> {
spacing: u16, spacing: u16,
padding: Padding, padding: Padding,
style: <crate::Theme as StyleSheet>::Style, style: crate::theme::Container<'a>,
children: Vec<Element<'a, Message>>, children: Vec<Element<'a, Message>>,
} }
@ -23,7 +23,7 @@ impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
Self { Self {
spacing: theme::THEME.lock().unwrap().cosmic().spacing.space_xxs, spacing: theme::THEME.lock().unwrap().cosmic().spacing.space_xxs,
padding: Padding::from(0), padding: Padding::from(0),
style: <crate::Theme as StyleSheet>::Style::List, style: crate::theme::Container::List,
children: Vec::with_capacity(4), children: Vec::with_capacity(4),
} }
} }
@ -41,9 +41,11 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
} }
// Ensure a minimum height of 32. // Ensure a minimum height of 32.
let container = crate::widget::container(item) let container = iced::widget::row![
.min_height(32) crate::widget::container(item).align_y(iced::alignment::Vertical::Center),
.align_y(iced::alignment::Vertical::Center); crate::widget::vertical_space().height(iced::Length::Fixed(32.))
]
.align_y(iced::alignment::Vertical::Center);
self.children.push(container.into()); self.children.push(container.into());
self self
@ -55,7 +57,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
} }
/// Sets the style variant of this [`Circular`]. /// Sets the style variant of this [`Circular`].
pub fn style(mut self, style: <crate::Theme as StyleSheet>::Style) -> Self { pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
self.style = style; self.style = style;
self self
} }
@ -72,7 +74,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
.padding(self.padding) .padding(self.padding)
.apply(super::container) .apply(super::container)
.padding([self.spacing, 8]) .padding([self.spacing, 8])
.style(self.style) .class(self.style)
.into() .into()
} }
} }

View file

@ -13,6 +13,6 @@ pub fn container<'a, Message>(
) -> Container<'a, Message, crate::Theme, crate::Renderer> { ) -> Container<'a, Message, crate::Theme, crate::Renderer> {
super::container(content) super::container(content)
.padding([16, 6]) .padding([16, 6])
.style(crate::theme::Container::List) .class(crate::theme::Container::List)
.width(iced::Length::Fill) .width(iced::Length::Fill)
} }

View file

@ -9,6 +9,7 @@ use super::{
}; };
use crate::style::menu_bar::StyleSheet; use crate::style::menu_bar::StyleSheet;
use iced::{Point, Vector};
use iced_core::Border; use iced_core::Border;
use iced_widget::core::{ use iced_widget::core::{
event, event,
@ -434,6 +435,7 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
let state = tree.state.downcast_ref::<MenuBarState>(); let state = tree.state.downcast_ref::<MenuBarState>();
if !state.open { if !state.open {
@ -455,6 +457,7 @@ where
root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(), root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(),
path_highlight: self.path_highlight, path_highlight: self.path_highlight,
style: &self.style, style: &self.style,
position: Point::new(translation.x, translation.y),
} }
.overlay(), .overlay(),
) )

View file

@ -276,7 +276,7 @@ impl MenuBounds {
let offset_bounds = Rectangle::new(offset_position, offset_size); let offset_bounds = Rectangle::new(offset_position, offset_size);
let children_bounds = Rectangle::new(children_position, children_size); let children_bounds = Rectangle::new(children_position, children_size);
let check_bounds = pad_rectangle(children_bounds, [bounds_expand; 4].into()); let check_bounds = pad_rectangle(children_bounds, bounds_expand.into());
Self { Self {
child_positions, child_positions,
@ -445,13 +445,14 @@ where
pub(crate) root_bounds_list: Vec<Rectangle>, pub(crate) root_bounds_list: Vec<Rectangle>,
pub(crate) path_highlight: Option<PathHighlight>, pub(crate) path_highlight: Option<PathHighlight>,
pub(crate) style: &'b <crate::Theme as StyleSheet>::Style, pub(crate) style: &'b <crate::Theme as StyleSheet>::Style,
pub(crate) position: Point,
} }
impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer> impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer>
where where
Renderer: renderer::Renderer, Renderer: renderer::Renderer,
{ {
pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> { pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> {
overlay::Element::new(Point::ORIGIN, Box::new(self)) overlay::Element::new(Box::new(self))
} }
} }
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer> impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
@ -459,14 +460,9 @@ impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer
where where
Renderer: renderer::Renderer, Renderer: renderer::Renderer,
{ {
fn layout( fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
_translation: iced::Vector,
) -> Node {
// layout children // layout children
let position = self.position;
let state = self.tree.state.downcast_mut::<MenuBarState>(); let state = self.tree.state.downcast_mut::<MenuBarState>();
let overlay_offset = Point::ORIGIN - position; let overlay_offset = Point::ORIGIN - position;
let tree_children = &mut self.tree.children; let tree_children = &mut self.tree.children;

View file

@ -146,14 +146,14 @@ pub fn menu_button<'a, Message: 'a>(
) -> crate::widget::Button<'a, Message> { ) -> crate::widget::Button<'a, Message> {
widget::button::custom( widget::button::custom(
widget::Row::with_children(children) widget::Row::with_children(children)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.height(Length::Fill) .height(Length::Fill)
.width(Length::Fill), .width(Length::Fill),
) )
.height(Length::Fixed(36.0)) .height(Length::Fixed(36.0))
.padding([4, 16]) .padding([4, 16])
.width(Length::Fill) .width(Length::Fill)
.style(theme::Button::MenuItem) .class(theme::Button::MenuItem)
} }
/// Represents a menu item that performs an action when selected or a separator between menu items. /// Represents a menu item that performs an action when selected or a separator between menu items.
@ -197,7 +197,7 @@ where
{ {
widget::button::custom(widget::text(label)) widget::button::custom(widget::text(label))
.padding([4, 12]) .padding([4, 12])
.style(theme::Button::MenuRoot) .class(theme::Button::MenuRoot)
.into() .into()
} }
@ -245,7 +245,7 @@ where
let key = find_key(&action, key_binds); let key = find_key(&action, key_binds);
let menu_button = menu_button(vec![ let menu_button = menu_button(vec![
widget::text(label).into(), widget::text(label).into(),
widget::horizontal_space(Length::Fill).into(), widget::horizontal_space().width(Length::Fill).into(),
widget::text(key).into(), widget::text(key).into(),
]) ])
.on_press(action.message()); .on_press(action.message());
@ -256,7 +256,7 @@ where
let key = find_key(&action, key_binds); let key = find_key(&action, key_binds);
let menu_button = menu_button(vec![ let menu_button = menu_button(vec![
widget::text(label).into(), widget::text(label).into(),
widget::horizontal_space(Length::Fill).into(), widget::horizontal_space().width(Length::Fill).into(),
widget::text(key).into(), widget::text(key).into(),
]); ]);
@ -270,8 +270,8 @@ where
widget::icon::from_name("object-select-symbolic") widget::icon::from_name("object-select-symbolic")
.size(16) .size(16)
.icon() .icon()
.style(theme::Svg::Custom(Rc::new(|theme| { .class(theme::Svg::Custom(Rc::new(|theme| {
crate::iced_style::svg::Appearance { iced_widget::svg::Style {
color: Some(theme.cosmic().accent_color().into()), color: Some(theme.cosmic().accent_color().into()),
} }
}))) })))
@ -282,9 +282,9 @@ where
}, },
widget::Space::with_width(Length::Fixed(8.0)).into(), widget::Space::with_width(Length::Fixed(8.0)).into(),
widget::text(label) widget::text(label)
.horizontal_alignment(iced::alignment::Horizontal::Left) .align_x(iced::alignment::Horizontal::Left)
.into(), .into(),
widget::horizontal_space(Length::Fill).into(), widget::horizontal_space().width(Length::Fill).into(),
widget::text(key).into(), widget::text(key).into(),
]) ])
.on_press(action.message()), .on_press(action.message()),
@ -294,13 +294,13 @@ where
trees.push(MenuTree::<Message, Renderer>::with_children( trees.push(MenuTree::<Message, Renderer>::with_children(
menu_button(vec![ menu_button(vec![
widget::text(label).into(), widget::text(label).into(),
widget::horizontal_space(Length::Fill).into(), widget::horizontal_space().width(Length::Fill).into(),
widget::icon::from_name("pan-end-symbolic") widget::icon::from_name("pan-end-symbolic")
.size(16) .size(16)
.icon() .icon()
.into(), .into(),
]) ])
.style( .class(
// Menu folders have no on_press so they take on the disabled style by default // Menu folders have no on_press so they take on the disabled style by default
if children.is_empty() { if children.is_empty() {
// This will make the folder use the disabled style if it has no children // This will make the folder use the disabled style if it has no children

View file

@ -0,0 +1,319 @@
mod subscription;
use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container;
use iced::Vector;
pub use subscription::*;
use iced_core::alignment;
use iced_core::event::{self, Event};
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
use std::{fmt::Debug, hash::Hash};
pub use iced_widget::container::{Catalog, Style};
pub fn min_size_tracker<'a, Message, I, T>(
content: T,
id: I,
tx: UnboundedSender<(I, Rectangle)>,
) -> MinSizeTrackerContainer<'a, Message, crate::Renderer, I>
where
I: Hash + Copy + Send + Sync + Debug + 'a,
T: Into<Element<'a, Message, crate::Theme, crate::Renderer>>,
{
MinSizeTrackerContainer::new(content, id, tx)
}
pub fn subscription<
I: 'static + Hash + Copy + Send + Sync + Debug,
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>(
id: I,
) -> iced::Subscription<(I, RectangleUpdate<R>)> {
subscription::rectangle_tracker_subscription(id)
}
#[derive(Clone, Debug)]
pub struct MinSizeTracker<I> {
tx: UnboundedSender<(I, Rectangle)>,
}
impl<I> MinSizeTracker<I>
where
I: Hash + Copy + Send + Sync + Debug,
{
pub fn container<'a, Message: 'static, T>(
&self,
id: I,
content: T,
) -> MinSizeTracker<'a, Message, crate::Renderer, I>
where
I: 'a,
T: Into<Element<'a, Message, crate::Theme, crate::Renderer>>,
{
MinSizeTracker::new(content, id, self.tx.clone())
}
}
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct MinSizeTrackerContainer<'a, Message, Renderer, I>
where
Renderer: iced_core::Renderer,
{
tx: UnboundedSender<(I, Rectangle)>,
id: I,
container: Container<'a, Message, crate::Theme, Renderer>,
ignore_bounds: bool,
}
impl<'a, Message, Renderer, I> MinSizeTrackerContainer<'a, Message, Renderer, I>
where
Renderer: iced_core::Renderer,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
/// Creates an empty [`Container`].
pub(crate) fn new<T>(content: T, id: I, tx: UnboundedSender<(I, Rectangle)>) -> Self
where
T: Into<Element<'a, Message, crate::Theme, Renderer>>,
{
MinSizeTrackerContainer {
id,
tx,
container: Container::new(content),
ignore_bounds: false,
}
}
pub fn diff(&mut self, tree: &mut Tree) {
self.container.diff(tree);
}
/// Sets the [`Padding`] of the [`Container`].
#[must_use]
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.container = self.container.padding(padding);
self
}
/// Sets the width of the [`self.`].
#[must_use]
pub fn width(mut self, width: Length) -> Self {
self.container = self.container.width(width);
self
}
/// Sets the height of the [`Container`].
#[must_use]
pub fn height(mut self, height: Length) -> Self {
self.container = self.container.height(height);
self
}
/// Sets the maximum width of the [`Container`].
#[must_use]
pub fn max_width(mut self, max_width: f32) -> Self {
self.container = self.container.max_width(max_width);
self
}
/// Sets the maximum height of the [`Container`] in pixels.
#[must_use]
pub fn max_height(mut self, max_height: f32) -> Self {
self.container = self.container.max_height(max_height);
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
#[must_use]
pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
self.container = self.container.align_x(alignment);
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
#[must_use]
pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
self.container = self.container.align_y(alignment);
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
#[must_use]
pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(width);
self
}
/// Centers the contents in the vertical axis of the [`Container`].
#[must_use]
pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(height);
self
}
/// Sets the style of the [`Container`].
#[must_use]
pub fn style(mut self, style: impl Into<<crate::Theme as Catalog>::Class<'a>>) -> Self {
self.container = self.container.class(style);
self
}
/// Set to true to ignore parent container bounds when performing layout.
/// This can be useful for widgets that are in auto-sized surfaces.
#[must_use]
pub fn ignore_bounds(mut self, ignore_bounds: bool) -> Self {
self.ignore_bounds = ignore_bounds;
self
}
}
impl<'a, Message, Renderer, I> Widget<Message, crate::Theme, Renderer>
for MinSizeTrackerContainer<'a, Message, Renderer, I>
where
Renderer: iced_core::Renderer,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
fn children(&self) -> Vec<Tree> {
self.container.children()
}
fn state(&self) -> iced_core::widget::tree::State {
self.container.state()
}
fn diff(&mut self, tree: &mut Tree) {
self.container.diff(tree);
}
fn size(&self) -> iced_core::Size<Length> {
self.container.size()
}
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.container.layout(
tree,
renderer,
if self.ignore_bounds {
&layout::Limits::NONE
} else {
limits
},
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<()>,
) {
self.container.operate(tree, layout, renderer, operation);
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &iced_core::Rectangle,
) -> event::Status {
self.container.on_event(
tree,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
viewport,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.container
.mouse_interaction(tree, layout, cursor_position, viewport, renderer)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &crate::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let _ = self.tx.unbounded_send((self.id, layout.bounds()));
self.container.draw(
tree,
renderer,
theme,
renderer_style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer, translation)
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
self.container
.drag_destinations(state, layout, renderer, dnd_rectangles);
}
}
impl<'a, Message, Renderer, I> From<MinSizeTrackerContainer<'a, Message, Renderer, I>>
for Element<'a, Message, crate::Theme, Renderer>
where
Message: 'a,
Renderer: 'a + iced_core::Renderer,
I: 'a + Hash + Copy + Send + Sync + Debug,
{
fn from(
column: MinSizeTrackerContainer<'a, Message, Renderer, I>,
) -> Element<'a, Message, crate::Theme, Renderer> {
Element::new(column)
}
}

View file

@ -0,0 +1,88 @@
use iced::{
futures::{
channel::mpsc::{unbounded, UnboundedReceiver},
stream, StreamExt,
},
Rectangle,
};
use iced_futures::Subscription;
use std::{collections::HashMap, fmt::Debug, hash::Hash};
use super::MinSizeTrackerContainer;
pub fn rectangle_tracker_subscription<
I: 'static + Hash + Copy + Send + Sync + Debug,
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>(
id: I,
) -> Subscription<(I, RectangleUpdate<R>)> {
Subscription::run_with_id(
id,
stream::unfold(State::Ready, move |state| start_listening(id, state)),
)
}
pub enum State<I> {
Ready,
Waiting(UnboundedReceiver<(I, Rectangle)>, HashMap<I, Rectangle>),
Finished,
}
async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug + Eq>(
id: I,
mut state: State<R>,
) -> Option<((I, RectangleUpdate<R>), State<R>)> {
loop {
let (update, new_state) = match state {
State::Ready => {
let (tx, rx) = unbounded();
(
Some((id, RectangleUpdate::Init(MinSizeTracker { tx }))),
State::Waiting(rx, HashMap::new()),
)
}
State::Waiting(mut rx, mut map) => match rx.next().await {
Some(u) => {
if let Some(prev) = map.get(&u.0) {
let new = u.1;
if (prev.width - new.width).abs() > 0.1
|| (prev.height - new.height).abs() > 0.1
|| (prev.x - new.x).abs() > 0.1
|| (prev.y - new.y).abs() > 0.1
{
map.insert(u.0, new);
(
Some((id, RectangleUpdate::Rectangle(u))),
State::Waiting(rx, map),
)
} else {
(None, State::Waiting(rx, map))
}
} else {
map.insert(u.0, u.1);
(
Some((id, RectangleUpdate::Rectangle(u))),
State::Waiting(rx, map),
)
}
}
None => (None, State::Finished),
},
State::Finished => return None,
};
state = new_state;
if let Some(u) = update {
return Some((u, state));
}
}
}
#[derive(Clone, Debug)]
pub enum RectangleUpdate<I>
where
I: 'static + Hash + Copy + Send + Sync + Debug,
{
Rectangle((I, Rectangle)),
Init(MinSizeTrackerContainer<I>),
}

View file

@ -60,7 +60,7 @@ pub use iced::widget::{combo_box, ComboBox};
pub use iced::widget::{container, Container}; pub use iced::widget::{container, Container};
#[doc(inline)] #[doc(inline)]
pub use iced::widget::{horizontal_space, space, vertical_space, Space}; pub use iced::widget::{horizontal_space, vertical_space, Space};
#[doc(inline)] #[doc(inline)]
pub use iced::widget::{image, Image}; pub use iced::widget::{image, Image};
@ -94,6 +94,9 @@ pub use iced_core::widget::{Id, Operation, Widget};
pub mod aspect_ratio; pub mod aspect_ratio;
#[cfg(feature = "autosize")]
pub mod autosize;
pub mod button; pub mod button;
#[doc(inline)] #[doc(inline)]
pub use button::{Button, IconButton, LinkButton, TextButton}; pub use button::{Button, IconButton, LinkButton, TextButton};
@ -166,20 +169,20 @@ pub mod divider {
/// Horizontal divider with default thickness /// Horizontal divider with default thickness
#[must_use] #[must_use]
pub fn default() -> Rule<crate::Theme> { pub fn default<'a>() -> Rule<'a, crate::Theme> {
horizontal_rule(1).style(crate::theme::Rule::Default) horizontal_rule(1).class(crate::theme::Rule::Default)
} }
/// Horizontal divider with light thickness /// Horizontal divider with light thickness
#[must_use] #[must_use]
pub fn light() -> Rule<crate::Theme> { pub fn light<'a>() -> Rule<'a, crate::Theme> {
horizontal_rule(1).style(crate::theme::Rule::LightDivider) horizontal_rule(1).class(crate::theme::Rule::LightDivider)
} }
/// Horizontal divider with heavy thickness. /// Horizontal divider with heavy thickness.
#[must_use] #[must_use]
pub fn heavy() -> Rule<crate::Theme> { pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
horizontal_rule(4).style(crate::theme::Rule::HeavyDivider) horizontal_rule(4).class(crate::theme::Rule::HeavyDivider)
} }
} }
@ -189,20 +192,20 @@ pub mod divider {
/// Vertical divider with default thickness /// Vertical divider with default thickness
#[must_use] #[must_use]
pub fn default() -> Rule<crate::Theme> { pub fn default<'a>() -> Rule<'a, crate::Theme> {
vertical_rule(1).style(crate::theme::Rule::Default) vertical_rule(1).class(crate::theme::Rule::Default)
} }
/// Vertical divider with light thickness /// Vertical divider with light thickness
#[must_use] #[must_use]
pub fn light() -> Rule<crate::Theme> { pub fn light<'a>() -> Rule<'a, crate::Theme> {
vertical_rule(4).style(crate::theme::Rule::LightDivider) vertical_rule(4).class(crate::theme::Rule::LightDivider)
} }
/// Vertical divider with heavy thickness. /// Vertical divider with heavy thickness.
#[must_use] #[must_use]
pub fn heavy() -> Rule<crate::Theme> { pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
vertical_rule(10).style(crate::theme::Rule::HeavyDivider) vertical_rule(10).class(crate::theme::Rule::HeavyDivider)
} }
} }
} }
@ -268,7 +271,7 @@ pub use radio::{radio, Radio};
pub mod rectangle_tracker; pub mod rectangle_tracker;
#[doc(inline)] #[doc(inline)]
pub use rectangle_tracker::{rectangle_tracker, RectangleTracker}; pub use rectangle_tracker::{rectangle_tracking_container, RectangleTracker};
#[doc(inline)] #[doc(inline)]
pub use row::{row, Row}; pub use row::{row, Row};
@ -343,13 +346,13 @@ pub mod tooltip {
pub fn tooltip<'a, Message>( pub fn tooltip<'a, Message>(
content: impl Into<Element<'a, Message>>, content: impl Into<Element<'a, Message>>,
tooltip: impl Into<Cow<'a, str>>, tooltip: impl Into<Element<'a, Message>>,
position: Position, position: Position,
) -> Tooltip<'a, Message> { ) -> Tooltip<'a, Message> {
let xxs = crate::theme::active().cosmic().space_xxs(); let xxs = crate::theme::active().cosmic().space_xxs();
Tooltip::new(content, tooltip, position) Tooltip::new(content, tooltip, position)
.style(crate::theme::Container::Tooltip) .class(crate::theme::Container::Tooltip)
.padding(xxs) .padding(xxs)
.gap(1) .gap(1)
} }

View file

@ -147,11 +147,12 @@ impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>>
.spacing(space_xxs) .spacing(space_xxs)
.style(crate::theme::SegmentedButton::TabBar) .style(crate::theme::SegmentedButton::TabBar)
.apply(scrollable) .apply(scrollable)
.class(crate::style::iced::Scrollable::Minimal)
.height(Length::Fill) .height(Length::Fill)
.apply(container) .apply(container)
.padding(space_xxs) .padding(space_xxs)
.height(Length::Fill) .height(Length::Fill)
.style(theme::Container::custom(nav_bar_style)) .class(theme::Container::custom(nav_bar_style))
} }
} }
@ -162,9 +163,9 @@ impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>> for crate::Element<
} }
#[must_use] #[must_use]
pub fn nav_bar_style(theme: &Theme) -> iced_style::container::Appearance { pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style {
let cosmic = &theme.cosmic(); let cosmic = &theme.cosmic();
iced_style::container::Appearance { iced_widget::container::Style {
icon_color: Some(cosmic.on_bg_color().into()), icon_color: Some(cosmic.on_bg_color().into()),
text_color: Some(cosmic.on_bg_color().into()), text_color: Some(cosmic.on_bg_color().into()),
background: Some(Background::Color(cosmic.primary.base.into())), background: Some(Background::Color(cosmic.primary.base.into())),

View file

@ -11,7 +11,7 @@ pub struct NavBarToggle<Message> {
active: bool, active: bool,
#[setters(strip_option)] #[setters(strip_option)]
on_toggle: Option<Message>, on_toggle: Option<Message>,
style: crate::theme::Button, class: crate::theme::Button,
selected: bool, selected: bool,
} }
@ -20,7 +20,7 @@ pub fn nav_bar_toggle<Message>() -> NavBarToggle<Message> {
NavBarToggle { NavBarToggle {
active: false, active: false,
on_toggle: None, on_toggle: None,
style: crate::theme::Button::Text, class: crate::theme::Button::Text,
selected: false, selected: false,
} }
} }
@ -43,7 +43,7 @@ impl<'a, Message: 'static + Clone> From<NavBarToggle<Message>> for Element<'a, M
.padding([8, 16]) .padding([8, 16])
.on_press_maybe(nav_bar_toggle.on_toggle) .on_press_maybe(nav_bar_toggle.on_toggle)
.selected(nav_bar_toggle.selected) .selected(nav_bar_toggle.selected)
.style(nav_bar_toggle.style) .class(nav_bar_toggle.class)
.into() .into()
} }
} }

View file

@ -9,12 +9,12 @@ use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::touch; use iced_core::touch;
use iced_core::widget::{tree, Operation, OperationOutputWrapper, Tree}; use iced_core::widget::{tree, Operation, Tree};
use iced_core::{ use iced_core::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
}; };
pub use iced_style::container::{Appearance, StyleSheet}; pub use iced_widget::container::{Catalog, Style};
pub fn popover<'a, Message, Renderer>( pub fn popover<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>, content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
@ -123,7 +123,7 @@ where
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget() .as_widget()
@ -214,6 +214,7 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
mut translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
if !tree.state.downcast_mut::<State>().is_open { if !tree.state.downcast_mut::<State>().is_open {
return None; return None;
@ -239,19 +240,22 @@ where
// Round position to prevent rendering issues // Round position to prevent rendering issues
overlay_position.x = overlay_position.x.round(); overlay_position.x = overlay_position.x.round();
overlay_position.y = overlay_position.y.round(); overlay_position.y = overlay_position.y.round();
translation.x += overlay_position.x;
translation.y += overlay_position.y;
Some(overlay::Element::new( Some(overlay::Element::new(Box::new(Overlay {
overlay_position, tree: &mut tree.children[1],
Box::new(Overlay { content: popup,
tree: &mut tree.children[1], position: self.position,
content: popup, pos: Point::new(translation.x, translation.y),
position: self.position, })))
}),
))
} else { } else {
self.content self.content.as_widget_mut().overlay(
.as_widget_mut() &mut tree.children[0],
.overlay(&mut tree.children[0], layout, renderer) layout,
renderer,
translation,
)
} }
} }
@ -286,6 +290,7 @@ pub struct Overlay<'a, 'b, Message, Renderer> {
tree: &'a mut Tree, tree: &'a mut Tree,
content: &'a mut Element<'b, Message, crate::Theme, Renderer>, content: &'a mut Element<'b, Message, crate::Theme, Renderer>,
position: Position, position: Position,
pos: Point,
} }
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer> impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
@ -294,13 +299,8 @@ where
Message: Clone, Message: Clone,
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
{ {
fn layout( fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
&mut self, let mut position = self.pos;
renderer: &Renderer,
bounds: Size,
mut position: Point,
_translation: iced::Vector,
) -> layout::Node {
let limits = layout::Limits::new(Size::UNIT, bounds); let limits = layout::Limits::new(Size::UNIT, bounds);
let node = self let node = self
.content .content
@ -342,7 +342,7 @@ where
&mut self, &mut self,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget() .as_widget()
@ -413,7 +413,7 @@ where
) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> {
self.content self.content
.as_widget_mut() .as_widget_mut()
.overlay(&mut self.tree, layout, renderer) .overlay(&mut self.tree, layout, renderer, Default::default())
} }
} }

View file

@ -1,4 +1,6 @@
//! Create choices using radio buttons. //! Create choices using radio buttons.
use crate::Theme;
use iced::border;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::layout; use iced_core::layout;
use iced_core::mouse; use iced_core::mouse;
@ -7,17 +9,18 @@ use iced_core::renderer;
use iced_core::touch; use iced_core::touch;
use iced_core::widget::tree::Tree; use iced_core::widget::tree::Tree;
use iced_core::{ use iced_core::{
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
}; };
pub use iced_style::radio::{Appearance, StyleSheet}; use iced_widget::radio as iced_radio;
pub use iced_widget::radio::Catalog;
pub fn radio<'a, Message: Clone, Theme: StyleSheet, V, F>( pub fn radio<'a, Message: Clone, V, F>(
label: impl Into<Element<'a, Message, Theme, crate::Renderer>>, label: impl Into<Element<'a, Message, Theme, crate::Renderer>>,
value: V, value: V,
selected: Option<V>, selected: Option<V>,
f: F, f: F,
) -> Radio<'a, Message, Theme, crate::Renderer> ) -> Radio<'a, Message, crate::Renderer>
where where
V: Eq + Copy, V: Eq + Copy,
F: FnOnce(V) -> Message, F: FnOnce(V) -> Message,
@ -83,9 +86,8 @@ where
/// let content = column![a, b, c, all]; /// let content = column![a, b, c, all];
/// ``` /// ```
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> pub struct Radio<'a, Message, Renderer = crate::Renderer>
where where
Theme: StyleSheet,
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
{ {
is_selected: bool, is_selected: bool,
@ -94,13 +96,11 @@ where
width: Length, width: Length,
size: f32, size: f32,
spacing: f32, spacing: f32,
style: Theme::Style,
} }
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer> impl<'a, Message, Renderer> Radio<'a, Message, Renderer>
where where
Message: Clone, Message: Clone,
Theme: StyleSheet,
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
{ {
/// The default size of a [`Radio`] button. /// The default size of a [`Radio`] button.
@ -130,7 +130,6 @@ where
width: Length::Shrink, width: Length::Shrink,
size: Self::DEFAULT_SIZE, size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING, spacing: Self::DEFAULT_SPACING,
style: Default::default(),
} }
} }
@ -154,20 +153,11 @@ where
self.spacing = spacing.into().0; self.spacing = spacing.into().0;
self self
} }
#[must_use]
/// Sets the style of the [`Radio`] button.
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
self.style = style.into();
self
}
} }
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Renderer> Widget<Message, Theme, Renderer> for Radio<'a, Message, Renderer>
for Radio<'a, Message, Theme, Renderer>
where where
Message: Clone, Message: Clone,
Theme: StyleSheet,
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
{ {
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
@ -207,9 +197,7 @@ where
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
self.label.as_widget().operate( self.label.as_widget().operate(
&mut tree.children[0], &mut tree.children[0],
@ -302,9 +290,19 @@ where
let mut children = layout.children(); let mut children = layout.children();
let custom_style = if is_mouse_over { let custom_style = if is_mouse_over {
theme.hovered(&self.style, self.is_selected) theme.style(
&(),
iced_radio::Status::Hovered {
is_selected: self.is_selected,
},
)
} else { } else {
theme.active(&self.style, self.is_selected) theme.style(
&(),
iced_radio::Status::Active {
is_selected: self.is_selected,
},
)
}; };
{ {
@ -336,7 +334,7 @@ where
width: dot_size, width: dot_size,
height: dot_size, height: dot_size,
}, },
border: Border::with_radius(dot_size / 2.0), border: border::rounded(dot_size / 2.0),
..renderer::Quad::default() ..renderer::Quad::default()
}, },
custom_style.dot_color, custom_style.dot_color,
@ -363,11 +361,13 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.label.as_widget_mut().overlay( self.label.as_widget_mut().overlay(
&mut tree.children[0], &mut tree.children[0],
layout.children().nth(1).unwrap(), layout.children().nth(1).unwrap(),
renderer, renderer,
translation,
) )
} }
@ -376,7 +376,7 @@ where
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.label.as_widget().drag_destinations( self.label.as_widget().drag_destinations(
&state.children[0], &state.children[0],
@ -387,14 +387,13 @@ where
} }
} }
impl<'a, Message, Theme, Renderer> From<Radio<'a, Message, Theme, Renderer>> impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
for Element<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a + Clone, Message: 'a + Clone,
Theme: 'a + StyleSheet,
Renderer: 'a + iced_core::Renderer, Renderer: 'a + iced_core::Renderer,
{ {
fn from(radio: Radio<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { fn from(radio: Radio<'a, Message, Renderer>) -> Element<'a, Message, Theme, Renderer> {
Element::new(radio) Element::new(radio)
} }
} }

View file

@ -2,6 +2,7 @@ mod subscription;
use iced::futures::channel::mpsc::UnboundedSender; use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container; use iced::widget::Container;
use iced::Vector;
pub use subscription::*; pub use subscription::*;
use iced_core::alignment; use iced_core::alignment;
@ -14,9 +15,9 @@ use iced_core::widget::Tree;
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget}; use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
use std::{fmt::Debug, hash::Hash}; use std::{fmt::Debug, hash::Hash};
pub use iced_style::container::{Appearance, StyleSheet}; pub use iced_widget::container::{Catalog, Style};
pub fn rectangle_tracker<'a, Message, I, T>( pub fn rectangle_tracking_container<'a, Message, I, T>(
content: T, content: T,
id: I, id: I,
tx: UnboundedSender<(I, Rectangle)>, tx: UnboundedSender<(I, Rectangle)>,
@ -71,6 +72,7 @@ where
id: I, id: I,
container: Container<'a, Message, crate::Theme, Renderer>, container: Container<'a, Message, crate::Theme, Renderer>,
ignore_bounds: bool, ignore_bounds: bool,
request_size: bool,
} }
impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I> impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I>
@ -88,6 +90,7 @@ where
tx, tx,
container: Container::new(content), container: Container::new(content),
ignore_bounds: false, ignore_bounds: false,
request_size: true,
} }
} }
@ -146,22 +149,22 @@ where
/// Centers the contents in the horizontal axis of the [`Container`]. /// Centers the contents in the horizontal axis of the [`Container`].
#[must_use] #[must_use]
pub fn center_x(mut self) -> Self { pub fn center_x(mut self, width: Length) -> Self {
self.container = self.container.center_x(); self.container = self.container.center_x(width);
self self
} }
/// Centers the contents in the vertical axis of the [`Container`]. /// Centers the contents in the vertical axis of the [`Container`].
#[must_use] #[must_use]
pub fn center_y(mut self) -> Self { pub fn center_y(mut self, height: Length) -> Self {
self.container = self.container.center_y(); self.container = self.container.center_y(height);
self self
} }
/// Sets the style of the [`Container`]. /// Sets the style of the [`Container`].
#[must_use] #[must_use]
pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self { pub fn style(mut self, style: impl Into<<crate::Theme as Catalog>::Class<'a>>) -> Self {
self.container = self.container.style(style); self.container = self.container.class(style);
self self
} }
@ -202,7 +205,7 @@ where
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
self.container.layout( let layout = self.container.layout(
tree, tree,
renderer, renderer,
if self.ignore_bounds { if self.ignore_bounds {
@ -210,7 +213,10 @@ where
} else { } else {
limits limits
}, },
) );
if self.request_size {}
layout
} }
fn operate( fn operate(
@ -218,9 +224,7 @@ where
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
self.container.operate(tree, layout, renderer, operation); self.container.operate(tree, layout, renderer, operation);
} }
@ -271,7 +275,6 @@ where
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let _ = self.tx.unbounded_send((self.id, layout.bounds())); let _ = self.tx.unbounded_send((self.id, layout.bounds()));
self.container.draw( self.container.draw(
tree, tree,
renderer, renderer,
@ -288,8 +291,9 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer) self.container.overlay(tree, layout, renderer, translation)
} }
fn drag_destinations( fn drag_destinations(
@ -297,7 +301,7 @@ where
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.container self.container
.drag_destinations(state, layout, renderer, dnd_rectangles); .drag_destinations(state, layout, renderer, dnd_rectangles);

View file

@ -1,10 +1,11 @@
use iced::{ use iced::{
futures::{ futures::{
channel::mpsc::{unbounded, UnboundedReceiver}, channel::mpsc::{unbounded, UnboundedReceiver},
StreamExt, stream, StreamExt,
}, },
subscription, Rectangle, Rectangle,
}; };
use iced_futures::Subscription;
use std::{collections::HashMap, fmt::Debug, hash::Hash}; use std::{collections::HashMap, fmt::Debug, hash::Hash};
use super::RectangleTracker; use super::RectangleTracker;
@ -14,8 +15,11 @@ pub fn rectangle_tracker_subscription<
R: 'static + Hash + Copy + Send + Sync + Debug + Eq, R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
>( >(
id: I, id: I,
) -> iced::Subscription<(I, RectangleUpdate<R>)> { ) -> Subscription<(I, RectangleUpdate<R>)> {
subscription::unfold(id, State::Ready, move |state| start_listening(id, state)) Subscription::run_with_id(
id,
stream::unfold(State::Ready, move |state| start_listening(id, state)),
)
} }
pub enum State<I> { pub enum State<I> {
@ -27,7 +31,7 @@ pub enum State<I> {
async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug + Eq>( async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug + Eq>(
id: I, id: I,
mut state: State<R>, mut state: State<R>,
) -> ((I, RectangleUpdate<R>), State<R>) { ) -> Option<((I, RectangleUpdate<R>), State<R>)> {
loop { loop {
let (update, new_state) = match state { let (update, new_state) = match state {
State::Ready => { State::Ready => {
@ -65,11 +69,11 @@ async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug
} }
None => (None, State::Finished), None => (None, State::Finished),
}, },
State::Finished => iced::futures::future::pending().await, State::Finished => return None,
}; };
state = new_state; state = new_state;
if let Some(u) = update { if let Some(u) = update {
return (u, state); return Some((u, state));
} }
} }
} }

View file

@ -16,14 +16,15 @@ use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, O
use iced::clipboard::mime::AllowedMimeTypes; use iced::clipboard::mime::AllowedMimeTypes;
use iced::touch::Finger; use iced::touch::Finger;
use iced::{ use iced::{
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length, alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Event, Length, Padding,
Padding, Rectangle, Size, Rectangle, Size, Task, Vector,
}; };
use iced_core::mouse::ScrollDelta; use iced_core::mouse::ScrollDelta;
use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrap}; use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping, Wrapping};
use iced_core::widget::{self, operation, tree}; use iced_core::widget::{self, operation, tree};
use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget}; use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text}; use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text};
use iced_runtime::{task, Action};
use slotmap::{Key, SecondaryMap}; use slotmap::{Key, SecondaryMap};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
@ -34,8 +35,8 @@ use std::mem;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
/// A command that focuses a segmented item stored in a widget. /// A command that focuses a segmented item stored in a widget.
pub fn focus<Message: 'static>(id: Id) -> Command<Message> { pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
Command::widget(operation::focusable::focus(id.0)) task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0))))
} }
pub enum ItemBounds { pub enum ItemBounds {
@ -436,15 +437,15 @@ where
if !text.is_empty() { if !text.is_empty() {
icon_spacing = f32::from(self.button_spacing); icon_spacing = f32::from(self.button_spacing);
let paragraph = entry.or_insert_with(|| { let paragraph = entry.or_insert_with(|| {
crate::Paragraph::with_text(Text { crate::Plain::new(Text {
content: text, content: text.as_ref(),
size: iced::Pixels(self.font_size), size: iced::Pixels(self.font_size),
bounds: Size::INFINITY, bounds: Size::INFINITY,
font, font,
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced, shaping: Shaping::Advanced,
wrap: Wrap::default(), wrapping: Wrapping::default(),
line_height: self.line_height, line_height: self.line_height,
}) })
}); });
@ -622,23 +623,21 @@ where
} }
let text = Text { let text = Text {
content: text, content: text.as_ref(),
size: iced::Pixels(self.font_size), size: iced::Pixels(self.font_size),
bounds: Size::INFINITY, bounds: Size::INFINITY,
font, font,
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced, shaping: Shaping::Advanced,
wrap: Wrap::default(), wrapping: Wrapping::default(),
line_height: self.line_height, line_height: self.line_height,
}; };
if let Some(paragraph) = state.paragraphs.get_mut(key) { if let Some(paragraph) = state.paragraphs.get_mut(key) {
paragraph.update(text); paragraph.update(text);
} else { } else {
state state.paragraphs.insert(key, crate::Plain::new(text));
.paragraphs
.insert(key, crate::Paragraph::with_text(text));
} }
} }
} }
@ -1079,9 +1078,7 @@ where
tree: &mut Tree, tree: &mut Tree,
_layout: Layout<'_>, _layout: Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation< operation: &mut dyn iced_core::widget::Operation<()>,
iced_core::widget::OperationOutputWrapper<Message>,
>,
) { ) {
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
operation.focusable(state, Some(&self.id.0)); operation.focusable(state, Some(&self.id.0));
@ -1492,7 +1489,7 @@ where
if self.model.text(key).is_some_and(|text| !text.is_empty()) { if self.model.text(key).is_some_and(|text| !text.is_empty()) {
// Draw the text for this segmented button or tab. // Draw the text for this segmented button or tab.
renderer.fill_paragraph( renderer.fill_paragraph(
&state.paragraphs[key], state.paragraphs[key].raw(),
bounds.position(), bounds.position(),
apply_alpha(status_appearance.text_color), apply_alpha(status_appearance.text_color),
Rectangle { Rectangle {
@ -1528,6 +1525,7 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: iced_core::Layout<'_>, layout: iced_core::Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
let state = tree.state.downcast_ref::<LocalState>(); let state = tree.state.downcast_ref::<LocalState>();
@ -1575,6 +1573,7 @@ where
root_bounds_list: vec![bounds], root_bounds_list: vec![bounds],
path_highlight: Some(PathHighlight::MenuActive), path_highlight: Some(PathHighlight::MenuActive),
style: &crate::theme::menu_bar::MenuBarStyle::Default, style: &crate::theme::menu_bar::MenuBarStyle::Default,
position: Point::new(translation.x, translation.y),
} }
.overlay(), .overlay(),
) )
@ -1645,7 +1644,7 @@ pub struct LocalState {
/// Dimensions of internal buttons when shrinking /// Dimensions of internal buttons when shrinking
pub(super) internal_layout: Vec<(Size, Size)>, pub(super) internal_layout: Vec<(Size, Size)>,
/// The paragraphs for each text. /// The paragraphs for each text.
paragraphs: SecondaryMap<Entity, crate::Paragraph>, paragraphs: SecondaryMap<Entity, crate::Plain>,
/// Used to detect changes in text. /// Used to detect changes in text.
text_hashes: SecondaryMap<Entity, u64>, text_hashes: SecondaryMap<Entity, u64>,
/// Location of cursor when context menu was opened. /// Location of cursor when context menu was opened.

View file

@ -9,7 +9,7 @@ use crate::{
Element, Element,
}; };
use derive_setters::Setters; use derive_setters::Setters;
use iced_core::{text::Wrap, Length}; use iced_core::{text::Wrapping, Length};
use taffy::AlignContent; use taffy::AlignContent;
/// A settings item aligned in a row /// A settings item aligned in a row
@ -20,8 +20,8 @@ pub fn item<'a, Message: 'static>(
widget: impl Into<Element<'a, Message>> + 'a, widget: impl Into<Element<'a, Message>> + 'a,
) -> Row<'a, Message> { ) -> Row<'a, Message> {
item_row(vec![ item_row(vec![
text(title).wrap(Wrap::Word).into(), text(title).wrapping(Wrapping::Word).into(),
horizontal_space(iced::Length::Fill).into(), horizontal_space().width(iced::Length::Fill).into(),
widget.into(), widget.into(),
]) ])
} }
@ -35,7 +35,7 @@ pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
} = theme::THEME.lock().unwrap().cosmic().spacing; } = theme::THEME.lock().unwrap().cosmic().spacing;
row::with_children(children) row::with_children(children)
.spacing(space_xs) .spacing(space_xs)
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.padding([0, space_s]) .padding([0, space_s])
} }
@ -46,7 +46,10 @@ pub fn flex_item<'a, Message: 'static>(
widget: impl Into<Element<'a, Message>> + 'a, widget: impl Into<Element<'a, Message>> + 'a,
) -> FlexRow<'a, Message> { ) -> FlexRow<'a, Message> {
flex_item_row(vec![ flex_item_row(vec![
text(title).wrap(Wrap::Word).width(Length::Fill).into(), text(title)
.wrapping(Wrapping::Word)
.width(Length::Fill)
.into(),
container(widget).into(), container(widget).into(),
]) ])
} }
@ -111,8 +114,8 @@ impl<'a, Message: 'static> Item<'a, Message> {
if let Some(description) = self.description { if let Some(description) = self.description {
let column = column::with_capacity(2) let column = column::with_capacity(2)
.spacing(2) .spacing(2)
.push(text(self.title).wrap(Wrap::Word)) .push(text(self.title).wrapping(Wrapping::Word))
.push(text(description).wrap(Wrap::Word).size(10)) .push(text(description).wrapping(Wrapping::Word).size(10))
.width(Length::Fill); .width(Length::Fill);
contents.push(column.into()); contents.push(column.into());
@ -129,6 +132,6 @@ impl<'a, Message: 'static> Item<'a, Message> {
is_checked: bool, is_checked: bool,
message: impl Fn(bool) -> Message + 'static, message: impl Fn(bool) -> Message + 'static,
) -> Row<'a, Message> { ) -> Row<'a, Message> {
self.control(crate::widget::toggler(None, is_checked, message)) self.control(crate::widget::toggler(is_checked, message))
} }
} }

View file

@ -56,11 +56,10 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
.apply(button::custom) .apply(button::custom)
.width(Length::Fixed(32.0)) .width(Length::Fixed(32.0))
.height(Length::Fixed(32.0)) .height(Length::Fixed(32.0))
.style(theme::Button::Text) .class(theme::Button::Text)
.on_press(model::Message::Decrement) .on_press(model::Message::Decrement)
.into(), .into(),
text::title4(label) text::title4(label)
.vertical_alignment(Vertical::Center)
.apply(container) .apply(container)
.width(Length::Fixed(48.0)) .width(Length::Fixed(48.0))
.align_x(Horizontal::Center) .align_x(Horizontal::Center)
@ -76,18 +75,18 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
.apply(button::custom) .apply(button::custom)
.width(Length::Fixed(32.0)) .width(Length::Fixed(32.0))
.height(Length::Fixed(32.0)) .height(Length::Fixed(32.0))
.style(theme::Button::Text) .class(theme::Button::Text)
.on_press(model::Message::Increment) .on_press(model::Message::Increment)
.into(), .into(),
]) ])
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Fixed(32.0)) .height(Length::Fixed(32.0))
.align_items(Alignment::Center), .align_y(Alignment::Center),
) )
.align_y(Vertical::Center) .align_y(Vertical::Center)
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Fixed(32.0)) .height(Length::Fixed(32.0))
.style(theme::Container::custom(container_style)) .class(theme::Container::custom(container_style))
.apply(Element::from) .apply(Element::from)
.map(on_change) .map(on_change)
} }
@ -100,13 +99,13 @@ impl<'a, Message: 'static> From<SpinButton<'a, Message>> for Element<'a, Message
} }
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
fn container_style(theme: &crate::Theme) -> iced_style::container::Appearance { fn container_style(theme: &crate::Theme) -> iced_widget::container::Style {
let basic = &theme.cosmic(); let basic = &theme.cosmic();
let mut neutral_10 = basic.palette.neutral_10; let mut neutral_10 = basic.palette.neutral_10;
neutral_10.alpha = 0.1; neutral_10.alpha = 0.1;
let accent = &basic.accent; let accent = &basic.accent;
let corners = &basic.corner_radii; let corners = &basic.corner_radii;
iced_style::container::Appearance { iced_widget::container::Style {
icon_color: Some(basic.palette.neutral_10.into()), icon_color: Some(basic.palette.neutral_10.into()),
text_color: Some(basic.palette.neutral_10.into()), text_color: Some(basic.palette.neutral_10.into()),
background: None, background: None,

View file

@ -7,7 +7,7 @@ use std::borrow::Cow;
/// ///
/// [`Text`]: widget::Text /// [`Text`]: widget::Text
pub fn text<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn text<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text).font(crate::font::default()) Text::new(text.into()).font(crate::font::default())
} }
/// Available presets for text typography /// Available presets for text typography
@ -26,7 +26,7 @@ pub enum Typography {
/// [`Text`] widget with the Title 1 typography preset. /// [`Text`] widget with the Title 1 typography preset.
pub fn title1<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn title1<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(32.0) .size(32.0)
.line_height(LineHeight::Absolute(44.0.into())) .line_height(LineHeight::Absolute(44.0.into()))
.font(crate::font::semibold()) .font(crate::font::semibold())
@ -34,7 +34,7 @@ pub fn title1<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Title 2 typography preset. /// [`Text`] widget with the Title 2 typography preset.
pub fn title2<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn title2<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(28.0) .size(28.0)
.line_height(LineHeight::Absolute(36.0.into())) .line_height(LineHeight::Absolute(36.0.into()))
.font(crate::font::default()) .font(crate::font::default())
@ -42,7 +42,7 @@ pub fn title2<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Title 3 typography preset. /// [`Text`] widget with the Title 3 typography preset.
pub fn title3<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn title3<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(24.0) .size(24.0)
.line_height(LineHeight::Absolute(32.0.into())) .line_height(LineHeight::Absolute(32.0.into()))
.font(crate::font::default()) .font(crate::font::default())
@ -50,7 +50,7 @@ pub fn title3<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Title 4 typography preset. /// [`Text`] widget with the Title 4 typography preset.
pub fn title4<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn title4<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(20.0) .size(20.0)
.line_height(LineHeight::Absolute(28.0.into())) .line_height(LineHeight::Absolute(28.0.into()))
.font(crate::font::default()) .font(crate::font::default())
@ -58,7 +58,7 @@ pub fn title4<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Heading typography preset. /// [`Text`] widget with the Heading typography preset.
pub fn heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(14.0) .size(14.0)
.line_height(LineHeight::Absolute(iced::Pixels(20.0))) .line_height(LineHeight::Absolute(iced::Pixels(20.0)))
.font(crate::font::semibold()) .font(crate::font::semibold())
@ -66,7 +66,7 @@ pub fn heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Caption Heading typography preset. /// [`Text`] widget with the Caption Heading typography preset.
pub fn caption_heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn caption_heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(10.0) .size(10.0)
.line_height(LineHeight::Absolute(iced::Pixels(14.0))) .line_height(LineHeight::Absolute(iced::Pixels(14.0)))
.font(crate::font::semibold()) .font(crate::font::semibold())
@ -74,7 +74,7 @@ pub fn caption_heading<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate
/// [`Text`] widget with the Body typography preset. /// [`Text`] widget with the Body typography preset.
pub fn body<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn body<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(14.0) .size(14.0)
.line_height(LineHeight::Absolute(20.0.into())) .line_height(LineHeight::Absolute(20.0.into()))
.font(crate::font::default()) .font(crate::font::default())
@ -82,7 +82,7 @@ pub fn body<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Re
/// [`Text`] widget with the Caption typography preset. /// [`Text`] widget with the Caption typography preset.
pub fn caption<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn caption<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(10.0) .size(10.0)
.line_height(LineHeight::Absolute(14.0.into())) .line_height(LineHeight::Absolute(14.0.into()))
.font(crate::font::default()) .font(crate::font::default())
@ -90,7 +90,7 @@ pub fn caption<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme,
/// [`Text`] widget with the Monotext typography preset. /// [`Text`] widget with the Monotext typography preset.
pub fn monotext<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> { pub fn monotext<'a>(text: impl Into<Cow<'a, str>> + 'a) -> Text<'a, crate::Theme, Renderer> {
Text::new(text) Text::new(text.into())
.size(14.0) .size(14.0)
.line_height(LineHeight::Absolute(20.0.into())) .line_height(LineHeight::Absolute(20.0.into()))
.font(crate::font::mono()) .font(crate::font::mono())

View file

@ -18,6 +18,9 @@ use super::style::StyleSheet;
pub use super::value::Value; pub use super::value::Value;
use apply::Apply; use apply::Apply;
use cosmic_theme::Theme;
use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
use iced::clipboard::mime::AsMimeTypes;
use iced::Limits; use iced::Limits;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::mouse::{self, click}; use iced_core::mouse::{self, click};
@ -39,15 +42,9 @@ use iced_core::{
}; };
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use iced_renderer::core::event::{wayland, PlatformSpecific}; use iced_renderer::core::event::{wayland, PlatformSpecific};
use iced_renderer::core::widget::OperationOutputWrapper;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use iced_runtime::command::platform_specific; use iced_runtime::platform_specific;
use iced_runtime::Command; use iced_runtime::{task, Action, Task};
#[cfg(feature = "wayland")]
use cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
#[cfg(feature = "wayland")]
use iced_runtime::command::platform_specific::wayland::data_device::{DataFromMimeType, DndIcon};
thread_local! { thread_local! {
// Prevents two inputs from being focused at the same time. // Prevents two inputs from being focused at the same time.
@ -146,7 +143,7 @@ where
}) })
.size(16) .size(16)
.apply(crate::widget::button::custom) .apply(crate::widget::button::custom)
.style(crate::theme::Button::Icon) .class(crate::theme::Button::Icon)
.on_press(msg) .on_press(msg)
.padding(8) .padding(8)
.into(), .into(),
@ -173,7 +170,6 @@ where
.padding(spacing) .padding(spacing)
} }
#[cfg(feature = "wayland")]
pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[ pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
"text/plain;charset=utf-8", "text/plain;charset=utf-8",
"text/plain;charset=UTF-8", "text/plain;charset=UTF-8",
@ -182,17 +178,12 @@ pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
"text/plain", "text/plain",
"TEXT", "TEXT",
]; ];
#[cfg(feature = "wayland")]
pub type DnDCommand =
Box<dyn Send + Sync + Fn() -> platform_specific::wayland::data_device::ActionInner>;
#[cfg(not(feature = "wayland"))]
pub type DnDCommand = ();
/// A field that can be filled with text. /// A field that can be filled with text.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
#[must_use] #[must_use]
pub struct TextInput<'a, Message> { pub struct TextInput<'a, Message> {
id: Option<Id>, id: Id,
placeholder: Cow<'a, str>, placeholder: Cow<'a, str>,
value: Value, value: Value,
is_secure: bool, is_secure: bool,
@ -215,7 +206,6 @@ pub struct TextInput<'a, Message> {
trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>, trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
style: <crate::Theme as StyleSheet>::Style, style: <crate::Theme as StyleSheet>::Style,
on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>, on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
on_dnd_command_produced: Option<Box<dyn Fn(DnDCommand) -> Message + 'a>>,
surface_ids: Option<(window::Id, window::Id)>, surface_ids: Option<(window::Id, window::Id)>,
dnd_icon: bool, dnd_icon: bool,
line_height: text::LineHeight, line_height: text::LineHeight,
@ -237,7 +227,7 @@ where
let v: Cow<'a, str> = value.into(); let v: Cow<'a, str> = value.into();
TextInput { TextInput {
id: None, id: Id::unique(),
placeholder: placeholder.into(), placeholder: placeholder.into(),
value: Value::new(v.as_ref()), value: Value::new(v.as_ref()),
is_secure: false, is_secure: false,
@ -258,7 +248,6 @@ where
trailing_icon: None, trailing_icon: None,
error: None, error: None,
style: crate::theme::TextInput::default(), style: crate::theme::TextInput::default(),
on_dnd_command_produced: None,
on_create_dnd_source: None, on_create_dnd_source: None,
surface_ids: None, surface_ids: None,
dnd_icon: false, dnd_icon: false,
@ -269,6 +258,15 @@ where
} }
} }
fn dnd_id(&self) -> u128 {
match &self.id.0 {
iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => {
*id as u128
}
_ => unreachable!(),
}
}
/// Sets the input to be always active. /// Sets the input to be always active.
/// This makes it behave as if it was always focused. /// This makes it behave as if it was always focused.
pub fn always_active(mut self) -> Self { pub fn always_active(mut self) -> Self {
@ -290,7 +288,7 @@ where
/// Sets the [`Id`] of the [`TextInput`]. /// Sets the [`Id`] of the [`TextInput`].
pub fn id(mut self, id: Id) -> Self { pub fn id(mut self, id: Id) -> Self {
self.id = Some(id); self.id = id;
self self
} }
@ -433,10 +431,12 @@ where
value: Option<&Value>, value: Option<&Value>,
style: &renderer::Style, style: &renderer::Style,
) { ) {
let text_layout = self.text_layout(layout.clone());
draw( draw(
renderer, renderer,
theme, theme,
layout, layout,
text_layout,
cursor_position, cursor_position,
tree, tree,
value.unwrap_or(&self.value), value.unwrap_or(&self.value),
@ -467,20 +467,6 @@ where
self self
} }
/// Sets the dnd command produced handler of the [`TextInput`].
/// Commands should be returned in the update function of the application.
#[cfg(feature = "wayland")]
pub fn on_dnd_command_produced(
mut self,
on_dnd_command_produced: impl Fn(
Box<dyn Send + Sync + Fn() -> platform_specific::wayland::data_device::ActionInner>,
) -> Message
+ 'a,
) -> Self {
self.on_dnd_command_produced = Some(Box::new(on_dnd_command_produced));
self
}
/// Sets the window id of the [`TextInput`] and the window id of the drag icon. /// Sets the window id of the [`TextInput`] and the window id of the drag icon.
/// Both ids are required to be unique. /// Both ids are required to be unique.
/// This is required for the dnd to work. /// This is required for the dnd to work.
@ -500,7 +486,7 @@ where
crate::widget::icon::from_name("edit-clear-symbolic") crate::widget::icon::from_name("edit-clear-symbolic")
.size(16) .size(16)
.apply(crate::widget::button::custom) .apply(crate::widget::button::custom)
.style(crate::theme::Button::Icon) .class(crate::theme::Button::Icon)
.on_press(on_clear) .on_press(on_clear)
.padding(8) .padding(8)
.into(), .into(),
@ -550,6 +536,7 @@ where
} }
let old_value = state let old_value = state
.value .value
.raw()
.buffer() .buffer()
.lines .lines
.iter() .iter()
@ -559,6 +546,7 @@ where
|| old_value != self.value.to_string() || old_value != self.value.to_string()
|| state || state
.label .label
.raw()
.buffer() .buffer()
.lines .lines
.iter() .iter()
@ -567,6 +555,7 @@ where
!= self.label.unwrap_or_default() != self.label.unwrap_or_default()
|| state || state
.helper_text .helper_text
.raw()
.buffer() .buffer()
.lines .lines
.iter() .iter()
@ -651,7 +640,7 @@ where
let v = self.value.to_string(); let v = self.value.to_string();
value_paragraph.update(Text { value_paragraph.update(Text {
content: if self.value.is_empty() { content: if self.value.is_empty() {
&self.placeholder self.placeholder.as_ref()
} else { } else {
&v &v
}, },
@ -662,7 +651,7 @@ where
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
line_height: text::LineHeight::default(), line_height: text::LineHeight::default(),
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
let Size { width, height } = let Size { width, height } =
@ -717,12 +706,13 @@ where
tree: &mut Tree, tree: &mut Tree,
_layout: Layout<'_>, _layout: Layout<'_>,
_renderer: &crate::Renderer, _renderer: &crate::Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
operation.focusable(state, self.id.as_ref()); operation.custom(state, Some(&self.id));
operation.text_input(state, self.id.as_ref()); operation.focusable(state, Some(&self.id));
operation.text_input(state, Some(&self.id));
} }
fn overlay<'b>( fn overlay<'b>(
@ -730,6 +720,7 @@ where
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let mut layout_ = Vec::with_capacity(2); let mut layout_ = Vec::with_capacity(2);
if self.leading_icon.is_some() { if self.leading_icon.is_some() {
@ -752,7 +743,9 @@ where
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout_) .zip(layout_)
.filter_map(|((child, state), layout)| { .filter_map(|((child, state), layout)| {
child.as_widget_mut().overlay(state, layout, renderer) child
.as_widget_mut()
.overlay(state, layout, renderer, translation)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -814,8 +807,10 @@ where
} }
} }
} }
let dnd_id = self.dnd_id();
let id = Widget::id(self);
update( update(
id,
event, event,
text_layout.children().next().unwrap(), text_layout.children().next().unwrap(),
trailing_icon_layout, trailing_icon_layout,
@ -833,9 +828,7 @@ where
self.on_toggle_edit.as_deref(), self.on_toggle_edit.as_deref(),
|| tree.state.downcast_mut::<State>(), || tree.state.downcast_mut::<State>(),
self.on_create_dnd_source.as_deref(), self.on_create_dnd_source.as_deref(),
self.dnd_icon, dnd_id,
self.on_dnd_command_produced.as_deref(),
self.surface_ids,
line_height, line_height,
layout, layout,
) )
@ -851,10 +844,12 @@ where
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let text_layout = self.text_layout(layout.clone());
draw( draw(
renderer, renderer,
theme, theme,
layout, layout,
text_layout,
cursor_position, cursor_position,
tree, tree,
&self.value, &self.value,
@ -934,11 +929,43 @@ where
} }
fn id(&self) -> Option<Id> { fn id(&self) -> Option<Id> {
self.id.clone() Some(self.id.clone())
} }
fn set_id(&mut self, id: Id) { fn set_id(&mut self, id: Id) {
self.id = Some(id); self.id = id;
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &crate::Renderer,
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) {
if let Some(input) = layout.children().last() {
let Rectangle {
x,
y,
width,
height,
} = input.bounds();
dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle {
id: self.dnd_id(),
rectangle: iced::clipboard::dnd::Rectangle {
x: x as f64,
y: y as f64,
width: width as f64,
height: height as f64,
},
mime_types: SUPPORTED_TEXT_MIME_TYPES
.iter()
.map(|s| Cow::Borrowed(*s))
.collect(),
actions: DndAction::Move,
preferred: DndAction::Move,
});
}
} }
} }
@ -954,32 +981,38 @@ where
} }
} }
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. /// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Command<Message> { pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
Command::widget(operation::focusable::focus(id)) task::effect(Action::widget(operation::focusable::focus(id)))
} }
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// end. /// end.
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> { pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
Command::widget(operation::text_input::move_cursor_to_end(id)) task::effect(Action::widget(operation::text_input::move_cursor_to_end(
id,
)))
} }
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// front. /// front.
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> { pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
Command::widget(operation::text_input::move_cursor_to_front(id)) task::effect(Action::widget(operation::text_input::move_cursor_to_front(
id,
)))
} }
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// provided position. /// provided position.
pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Command<Message> { pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
Command::widget(operation::text_input::move_cursor_to(id, position)) task::effect(Action::widget(operation::text_input::move_cursor_to(
id, position,
)))
} }
/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. /// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
pub fn select_all<Message: 'static>(id: Id) -> Command<Message> { pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
Command::widget(operation::text_input::select_all(id)) task::effect(Action::widget(operation::text_input::select_all(id)))
} }
/// Computes the layout of a [`TextInput`]. /// Computes the layout of a [`TextInput`].
@ -1019,7 +1052,7 @@ pub fn layout<Message>(
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
line_height, line_height,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
let label_size = label_paragraph.min_bounds(); let label_size = label_paragraph.min_bounds();
@ -1158,7 +1191,7 @@ pub fn layout<Message>(
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
line_height: helper_text_line_height, line_height: helper_text_line_height,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
let helper_text_size = helper_text_paragraph.min_bounds(); let helper_text_size = helper_text_paragraph.min_bounds();
let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos); let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos);
@ -1188,7 +1221,8 @@ pub fn layout<Message>(
#[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_panics_doc)]
#[allow(clippy::cast_lossless)] #[allow(clippy::cast_lossless)]
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
pub fn update<'a, Message>( pub fn update<'a, Message: 'static>(
id: Option<Id>,
event: Event, event: Event,
text_layout: Layout<'_>, text_layout: Layout<'_>,
trailing_icon_layout: Option<Layout<'_>>, trailing_icon_layout: Option<Layout<'_>>,
@ -1206,9 +1240,7 @@ pub fn update<'a, Message>(
on_toggle_edit: Option<&dyn Fn(bool) -> Message>, on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
state: impl FnOnce() -> &'a mut State, state: impl FnOnce() -> &'a mut State,
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>, #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
#[allow(unused_variables)] dnd_icon: bool, #[allow(unused_variables)] dnd_id: u128,
#[allow(unused_variables)] on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>,
#[allow(unused_variables)] surface_ids: Option<(window::Id, window::Id)>,
line_height: text::LineHeight, line_height: text::LineHeight,
layout: Layout<'_>, layout: Layout<'_>,
) -> event::Status ) -> event::Status
@ -1253,7 +1285,8 @@ where
let text_layout = layout.children().next().unwrap(); let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x; let target = cursor_position.x - text_layout.bounds().x;
let click = mouse::Click::new(cursor_position, state.last_click); let click =
mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);
match ( match (
&state.dragging_state, &state.dragging_state,
@ -1266,28 +1299,18 @@ where
// single click that is on top of the selected text // single click that is on top of the selected text
// is the click on selected text? // is the click on selected text?
if let ( if let Some(on_input) = on_input {
Some(on_start_dnd),
Some(on_dnd_command_produced),
Some((window_id, icon_id)),
Some(on_input),
) = (
on_start_dnd_source,
on_dnd_command_produced,
surface_ids,
on_input,
) {
let left = start.min(end); let left = start.min(end);
let right = end.max(start); let right = end.max(start);
let (left_position, _left_offset) = measure_cursor_and_scroll_offset( let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
&state.value, state.value.raw(),
text_layout.bounds(), text_layout.bounds(),
left, left,
); );
let (right_position, _right_offset) = measure_cursor_and_scroll_offset( let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
&state.value, state.value.raw(),
text_layout.bounds(), text_layout.bounds(),
right, right,
); );
@ -1305,10 +1328,12 @@ where
if is_secure { if is_secure {
return event::Status::Ignored; return event::Status::Ignored;
} }
let text = let input_text =
state.selected_text(&value.to_string()).unwrap_or_default(); state.selected_text(&value.to_string()).unwrap_or_default();
state.dragging_state = state.dragging_state = Some(DraggingState::Dnd(
Some(DraggingState::Dnd(DndAction::empty(), text.clone())); DndAction::empty(),
input_text.clone(),
));
let mut editor = Editor::new(unsecured_value, &mut state.cursor); let mut editor = Editor::new(unsecured_value, &mut state.cursor);
editor.delete(); editor.delete();
@ -1316,23 +1341,25 @@ where
let unsecured_value = Value::new(&contents); let unsecured_value = Value::new(&contents);
let message = (on_input)(contents); let message = (on_input)(contents);
shell.publish(message); shell.publish(message);
shell.publish(on_start_dnd(state.clone())); if let Some(on_start_dnd) = on_start_dnd_source {
shell.publish(on_start_dnd(state.clone()));
}
let state_clone = state.clone(); let state_clone = state.clone();
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::StartDnd { iced_core::clipboard::start_dnd(
mime_types: SUPPORTED_TEXT_MIME_TYPES clipboard,
.iter() false,
.map(std::string::ToString::to_string) id.map(|id| iced_core::clipboard::DndSource::Widget(id)),
.collect(), Some((
actions: DndAction::Move, Element::from(
origin_id: window_id, TextInput::<'static, ()>::new("", input_text.clone())
icon_id: Some(( .dnd_icon(true),
DndIcon::Widget(icon_id, Box::new(state_clone.clone())), ),
iced::Vector::ZERO, iced_core::widget::tree::State::new(state_clone),
)), )),
data: Box::new(TextInputString(text.clone())), Box::new(TextInputString(input_text)),
} DndAction::Move,
}))); );
update_cache(state, &unsecured_value); update_cache(state, &unsecured_value);
} else { } else {
@ -1435,7 +1462,7 @@ where
return event::Status::Ignored; return event::Status::Ignored;
}; };
let click = mouse::Click::new(pos, state.last_click); let click = mouse::Click::new(pos, mouse::Button::Left, state.last_click);
match ( match (
&state.dragging_state, &state.dragging_state,
@ -1621,7 +1648,10 @@ where
{ {
if !is_secure { if !is_secure {
if let Some((start, end)) = state.cursor.selection(value) { if let Some((start, end)) = state.cursor.selection(value) {
clipboard.write(value.select(start, end).to_string()); clipboard.write(
iced_core::clipboard::Kind::Primary,
value.select(start, end).to_string(),
);
} }
} }
} }
@ -1632,7 +1662,10 @@ where
{ {
if !is_secure { if !is_secure {
if let Some((start, end)) = state.cursor.selection(value) { if let Some((start, end)) = state.cursor.selection(value) {
clipboard.write(value.select(start, end).to_string()); clipboard.write(
iced_core::clipboard::Kind::Primary,
value.select(start, end).to_string(),
);
} }
let mut editor = Editor::new(value, &mut state.cursor); let mut editor = Editor::new(value, &mut state.cursor);
@ -1650,7 +1683,7 @@ where
content content
} else { } else {
let content: String = clipboard let content: String = clipboard
.read() .read(iced_core::clipboard::Kind::Primary)
.unwrap_or_default() .unwrap_or_default()
.chars() .chars()
.filter(|c| !c.is_control()) .filter(|c| !c.is_control())
@ -1760,7 +1793,7 @@ where
state.keyboard_modifiers = modifiers; state.keyboard_modifiers = modifiers;
} }
Event::Window(_, window::Event::RedrawRequested(now)) => { Event::Window(window::Event::RedrawRequested(now)) => {
let state = state(); let state = state();
if let Some(focus) = &mut state.is_focused { if let Some(focus) = &mut state.is_focused {
@ -1775,9 +1808,7 @@ where
} }
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource( Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
wayland::DataSourceEvent::DndFinished | wayland::DataSourceEvent::Cancelled,
))) => {
let state = state(); let state = state();
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) { if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
state.dragging_state = None; state.dragging_state = None;
@ -1785,54 +1816,32 @@ where
} }
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource( Event::Dnd(DndEvent::Offer(
wayland::DataSourceEvent::DndActionAccepted(action), rectangle,
))) => { OfferEvent::Enter {
let state = state(); x,
if let Some(DraggingState::Dnd(_, text)) = state.dragging_state.as_ref() { y,
state.dragging_state = Some(DraggingState::Dnd(action, text.clone())); mime_types,
return event::Status::Captured; surface,
} },
} )) if rectangle == Some(dnd_id) => {
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::Enter { x, y, mime_types },
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
let state = state(); let state = state();
let is_clicked = text_layout.bounds().contains(Point { let is_clicked = text_layout.bounds().contains(Point {
x: x as f32, x: x as f32,
y: y as f32, y: y as f32,
}); });
if !is_clicked {
state.dnd_offer = DndOfferState::OutsideWidget(mime_types, DndAction::None);
return event::Status::Captured;
}
let mut accepted = false; let mut accepted = false;
for m in &mime_types { for m in &mime_types {
if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) { if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
let clone = m.clone(); let clone = m.clone();
accepted = true; accepted = true;
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::Accept(Some(
clone.clone(),
))
})));
} }
} }
if accepted { if accepted {
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: DndAction::Move,
accepted: DndAction::Move.union(DndAction::Copy),
}
})));
let target = x as f32 - text_layout.bounds().x; let target = x as f32 - text_layout.bounds().x;
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::None); state.dnd_offer =
DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
// existing logic for setting the selection // existing logic for setting the selection
let position = if target > 0.0 { let position = if target > 0.0 {
update_cache(state, value); update_cache(state, value);
@ -1846,57 +1855,11 @@ where
} }
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
wayland::DndOfferEvent::Motion { x, y }, if rectangle == Some(dnd_id) =>
))) => { {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
let state = state(); let state = state();
let is_clicked = text_layout.bounds().contains(Point {
x: x as f32,
y: y as f32,
});
if !is_clicked {
if let DndOfferState::HandlingOffer(mime_types, action) = state.dnd_offer.clone() {
state.dnd_offer = DndOfferState::OutsideWidget(mime_types, action);
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: DndAction::None,
accepted: DndAction::None,
}
})));
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::Accept(None)
})));
}
return event::Status::Captured;
} else if let DndOfferState::OutsideWidget(mime_types, action) = state.dnd_offer.clone()
{
let mut accepted = false;
for m in &mime_types {
if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
accepted = true;
let clone = m.clone();
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::Accept(Some(
clone.clone(),
))
})));
}
}
if accepted {
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: DndAction::Move,
accepted: DndAction::Move.union(DndAction::Copy),
}
})));
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), action);
}
};
let target = x as f32 - text_layout.bounds().x; let target = x as f32 - text_layout.bounds().x;
// existing logic for setting the selection // existing logic for setting the selection
let position = if target > 0.0 { let position = if target > 0.0 {
@ -1910,13 +1873,7 @@ where
return event::Status::Captured; return event::Status::Captured;
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
wayland::DndOfferEvent::DropPerformed,
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
let state = state(); let state = state();
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() { if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
@ -1927,21 +1884,15 @@ where
return event::Status::Captured; return event::Status::Captured;
}; };
state.dnd_offer = DndOfferState::Dropped; state.dnd_offer = DndOfferState::Dropped;
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::RequestDndData(
(*mime_type).to_string(),
)
})));
} else if let DndOfferState::OutsideWidget(..) = &state.dnd_offer {
state.dnd_offer = DndOfferState::None;
return event::Status::Captured;
} }
return event::Status::Ignored; return event::Status::Ignored;
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( Event::Dnd(DndEvent::Offer(
wayland::DndOfferEvent::Leave, rectangle,
))) => { OfferEvent::Leave | OfferEvent::LeaveDestination,
)) if rectangle == Some(dnd_id) => {
let state = state(); let state = state();
// ASHLEY TODO we should be able to reset but for now we don't if we are handling a // ASHLEY TODO we should be able to reset but for now we don't if we are handling a
// drop // drop
@ -1954,13 +1905,9 @@ where
return event::Status::Captured; return event::Status::Captured;
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer( Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
wayland::DndOfferEvent::DndData { mime_type, data }, if rectangle == Some(dnd_id) =>
))) => { {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
let state = state(); let state = state();
if let DndOfferState::Dropped = state.dnd_offer.clone() { if let DndOfferState::Dropped = state.dnd_offer.clone() {
state.dnd_offer = DndOfferState::None; state.dnd_offer = DndOfferState::None;
@ -1982,9 +1929,6 @@ where
shell.publish(message); shell.publish(message);
} }
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::DndFinished
})));
let value = if is_secure { let value = if is_secure {
unsecured_value.secure() unsecured_value.secure()
} else { } else {
@ -1995,26 +1939,6 @@ where
} }
return event::Status::Ignored; return event::Status::Ignored;
} }
#[cfg(feature = "wayland")]
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
wayland::DndOfferEvent::SourceActions(actions),
))) => {
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
return event::Status::Ignored;
};
let state = state();
if let DndOfferState::HandlingOffer(..) = state.dnd_offer.clone() {
shell.publish(on_dnd_command_produced(Box::new(move || {
platform_specific::wayland::data_device::ActionInner::SetActions {
preferred: actions.intersection(DndAction::Move),
accepted: actions,
}
})));
return event::Status::Captured;
}
return event::Status::Ignored;
}
_ => {} _ => {}
} }
@ -2032,6 +1956,7 @@ pub fn draw<'a, Message>(
renderer: &mut crate::Renderer, renderer: &mut crate::Renderer,
theme: &crate::Theme, theme: &crate::Theme,
layout: Layout<'_>, layout: Layout<'_>,
text_layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
tree: &Tree, tree: &Tree,
value: &Value, value: &Value,
@ -2083,7 +2008,7 @@ pub fn draw<'a, Message>(
let mut children_layout = layout.children(); let mut children_layout = layout.children();
let bounds = layout.bounds(); let bounds = layout.bounds();
let text_bounds = children_layout.next().unwrap().bounds(); let text_bounds = text_layout.bounds();
let is_mouse_over = cursor_position.is_over(bounds); let is_mouse_over = cursor_position.is_over(bounds);
@ -2172,7 +2097,7 @@ pub fn draw<'a, Message>(
if let (Some(label_layout), Some(label)) = (label_layout, label) { if let (Some(label_layout), Some(label)) = (label_layout, label) {
renderer.fill_text( renderer.fill_text(
Text { Text {
content: label, content: label.to_string(),
size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)), size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
font: font.unwrap_or_else(|| renderer.default_font()), font: font.unwrap_or_else(|| renderer.default_font()),
bounds: label_layout.bounds().size(), bounds: label_layout.bounds().size(),
@ -2180,7 +2105,7 @@ pub fn draw<'a, Message>(
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
line_height, line_height,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
label_layout.bounds().position(), label_layout.bounds().position(),
appearance.label_color, appearance.label_color,
@ -2214,14 +2139,24 @@ pub fn draw<'a, Message>(
let size = size.unwrap_or_else(|| renderer.default_size().0); let size = size.unwrap_or_else(|| renderer.default_size().0);
let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into(); let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
let (cursor, offset) = if let Some(focus) = &state.is_focused { #[cfg(feature = "wayland")]
let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
#[cfg(not(feature = "wayland"))]
let handling_dnd_offer = false;
let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| {
handling_dnd_offer.then(|| Focus {
updated_at: Instant::now(),
now: Instant::now(),
})
}) {
match state.cursor.state(value) { match state.cursor.state(value) {
cursor::State::Index(position) => { cursor::State::Index(position) => {
let (text_value_width, offset) = let (text_value_width, offset) =
measure_cursor_and_scroll_offset(&state.value, text_bounds, position); measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position);
let is_cursor_visible = let is_cursor_visible = handling_dnd_offer
((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2 || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
% 2
== 0; == 0;
if is_cursor_visible { if is_cursor_visible {
@ -2263,10 +2198,10 @@ pub fn draw<'a, Message>(
let value_paragraph = &state.value; let value_paragraph = &state.value;
let (left_position, left_offset) = let (left_position, left_offset) =
measure_cursor_and_scroll_offset(value_paragraph, text_bounds, left); measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left);
let (right_position, right_offset) = let (right_position, right_offset) =
measure_cursor_and_scroll_offset(value_paragraph, text_bounds, right); measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right);
let width = right_position - left_position; let width = right_position - left_position;
@ -2331,7 +2266,11 @@ pub fn draw<'a, Message>(
renderer.fill_text( renderer.fill_text(
Text { Text {
content: if text.is_empty() { placeholder } else { &text }, content: if text.is_empty() {
placeholder.to_string()
} else {
text.clone()
},
font, font,
bounds: bounds.size(), bounds: bounds.size(),
size: iced::Pixels(size), size: iced::Pixels(size),
@ -2339,7 +2278,7 @@ pub fn draw<'a, Message>(
vertical_alignment: alignment::Vertical::Center, vertical_alignment: alignment::Vertical::Center,
line_height: text::LineHeight::default(), line_height: text::LineHeight::default(),
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
bounds.position(), bounds.position(),
color, color,
@ -2374,7 +2313,7 @@ pub fn draw<'a, Message>(
if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) { if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
renderer.fill_text( renderer.fill_text(
Text { Text {
content: helper_text, content: helper_text.to_string(), // TODO remove to_string?
size: iced::Pixels(helper_text_size), size: iced::Pixels(helper_text_size),
font, font,
bounds: helper_text_layout.bounds().size(), bounds: helper_text_layout.bounds().size(),
@ -2382,7 +2321,7 @@ pub fn draw<'a, Message>(
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
line_height: helper_line_height, line_height: helper_line_height,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}, },
helper_text_layout.bounds().position(), helper_text_layout.bounds().position(),
text_color, text_color,
@ -2414,11 +2353,23 @@ pub fn mouse_interaction(
pub struct TextInputString(pub String); pub struct TextInputString(pub String);
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
impl DataFromMimeType for TextInputString { impl AsMimeTypes for TextInputString {
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> { fn available(&self) -> Cow<'static, [String]> {
SUPPORTED_TEXT_MIME_TYPES Cow::Owned(
.contains(&mime_type) SUPPORTED_TEXT_MIME_TYPES
.then(|| self.0.as_bytes().to_vec()) .iter()
.cloned()
.map(String::from)
.collect::<Vec<_>>(),
)
}
fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
Some(Cow::Owned(self.0.clone().as_bytes().to_vec()))
} else {
None
}
} }
} }
@ -2434,7 +2385,6 @@ pub(crate) enum DraggingState {
pub(crate) enum DndOfferState { pub(crate) enum DndOfferState {
#[default] #[default]
None, None,
OutsideWidget(Vec<String>, DndAction),
HandlingOffer(Vec<String>, DndAction), HandlingOffer(Vec<String>, DndAction),
Dropped, Dropped,
} }
@ -2446,17 +2396,16 @@ pub(crate) struct DndOfferState;
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
#[must_use] #[must_use]
pub struct State { pub struct State {
pub value: crate::Paragraph, pub value: crate::Plain,
pub placeholder: crate::Paragraph, pub placeholder: crate::Plain,
pub label: crate::Paragraph, pub label: crate::Plain,
pub helper_text: crate::Paragraph, pub helper_text: crate::Plain,
pub dirty: bool, pub dirty: bool,
pub is_secure: bool, pub is_secure: bool,
pub is_read_only: bool, pub is_read_only: bool,
select_on_focus: bool, select_on_focus: bool,
is_focused: Option<Focus>, is_focused: Option<Focus>,
dragging_state: Option<DraggingState>, dragging_state: Option<DraggingState>,
#[cfg(feature = "wayland")]
dnd_offer: DndOfferState, dnd_offer: DndOfferState,
is_pasting: Option<Value>, is_pasting: Option<Value>,
last_click: Option<mouse::Click>, last_click: Option<mouse::Click>,
@ -2522,15 +2471,14 @@ impl State {
pub fn focused(is_secure: bool, is_read_only: bool) -> Self { pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
Self { Self {
is_secure, is_secure,
value: crate::Paragraph::new(), value: crate::Plain::default(),
placeholder: crate::Paragraph::new(), placeholder: crate::Plain::default(),
label: crate::Paragraph::new(), label: crate::Plain::default(),
helper_text: crate::Paragraph::new(), helper_text: crate::Plain::default(),
is_read_only, is_read_only,
is_focused: None, is_focused: None,
select_on_focus: false, select_on_focus: false,
dragging_state: None, dragging_state: None,
#[cfg(feature = "wayland")]
dnd_offer: DndOfferState::default(), dnd_offer: DndOfferState::default(),
is_pasting: None, is_pasting: None,
last_click: None, last_click: None,
@ -2654,6 +2602,7 @@ fn find_cursor_position(
let char_offset = state let char_offset = state
.value .value
.raw()
.hit_test(Point::new(x + offset, text_bounds.height / 2.0)) .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
.map(text::Hit::cursor)?; .map(text::Hit::cursor)?;
@ -2677,7 +2626,7 @@ fn replace_paragraph(
let mut children_layout = layout.children(); let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds(); let text_bounds = children_layout.next().unwrap().bounds();
state.value = crate::Paragraph::with_text(Text { state.value = crate::Plain::new(Text {
font, font,
line_height, line_height,
content: &value.to_string(), content: &value.to_string(),
@ -2686,7 +2635,7 @@ fn replace_paragraph(
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
wrap: text::Wrap::default(), wrapping: text::Wrapping::default(),
}); });
} }
@ -2714,7 +2663,7 @@ fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
}; };
let (_, offset) = let (_, offset) =
measure_cursor_and_scroll_offset(&state.value, text_bounds, focus_position); measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);
offset offset
} else { } else {

View file

@ -8,7 +8,7 @@ use std::rc::Rc;
use crate::widget::container; use crate::widget::container;
use crate::widget::Column; use crate::widget::Column;
use crate::Command; use iced::{Padding, Task};
use iced_core::Element; use iced_core::Element;
use slotmap::new_key_type; use slotmap::new_key_type;
use slotmap::SlotMap; use slotmap::SlotMap;
@ -47,15 +47,15 @@ pub fn toaster<'a, Message: Clone + 'static>(
button::icon(icon::from_name("window-close-symbolic")) button::icon(icon::from_name("window-close-symbolic"))
.on_press((toasts.on_close)(id)), .on_press((toasts.on_close)(id)),
) )
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.spacing(space_xxs), .spacing(space_xxs),
) )
.align_items(iced::Alignment::Center) .align_y(iced::Alignment::Center)
.spacing(space_s); .spacing(space_s);
container(row) container(row)
.padding([space_xxs, space_s, space_xxs, space_m]) .padding([space_xxs, space_s, space_xxs, space_m])
.style(crate::style::Container::Tooltip) .class(crate::style::Container::Tooltip)
}; };
let col = toasts let col = toasts
@ -175,7 +175,7 @@ impl<Message: Clone + Send + 'static> Toasts<Message> {
} }
/// Add a new [`Toast`] /// Add a new [`Toast`]
pub fn push(&mut self, toast: Toast<Message>) -> Command<Message> { pub fn push(&mut self, toast: Toast<Message>) -> Task<Message> {
while self.toasts.len() >= self.limit { while self.toasts.len() >= self.limit {
self.toasts.remove( self.toasts.remove(
self.queue self.queue
@ -200,7 +200,7 @@ impl<Message: Clone + Send + 'static> Toasts<Message> {
} }
#[cfg(not(feature = "tokio"))] #[cfg(not(feature = "tokio"))]
{ {
Command::none() Task::none()
} }
} }

View file

@ -14,7 +14,6 @@ use iced_core::widget::Operation;
use iced_core::Element; use iced_core::Element;
use iced_core::Overlay; use iced_core::Overlay;
use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget}; use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget};
use iced_renderer::core::widget::OperationOutputWrapper;
pub struct Toaster<'a, Message, Theme, Renderer> { pub struct Toaster<'a, Message, Theme, Renderer> {
toasts: Element<'a, Message, Theme, Renderer>, toasts: Element<'a, Message, Theme, Renderer>,
@ -90,7 +89,7 @@ where
state: &'b mut Tree, state: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>, operation: &mut dyn Operation<()>,
) { ) {
self.content self.content
.as_widget() .as_widget()
@ -142,22 +141,23 @@ where
state: &'b mut Tree, state: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
//TODO: this hides the overlay of the content during the toast //TODO: this hides the overlay of the content during the toast
if self.is_empty { if self.is_empty {
self.content self.content.as_widget_mut().overlay(
.as_widget_mut() &mut state.children[0],
.overlay(&mut state.children[0], layout, renderer) layout,
renderer,
translation,
)
} else { } else {
let bounds = layout.bounds(); let bounds = layout.bounds();
Some(overlay::Element::new( Some(overlay::Element::new(Box::new(ToasterOverlay::new(
bounds.position(), &mut state.children[1],
Box::new(ToasterOverlay::new( &mut self.toasts,
&mut state.children[1], ))))
&mut self.toasts,
)),
))
} }
} }
@ -166,7 +166,7 @@ where
state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.content.as_widget().drag_destinations( self.content.as_widget().drag_destinations(
&state.children[0], &state.children[0],
@ -196,13 +196,7 @@ impl<'a, 'b, Message, Theme, Renderer> Overlay<Message, Theme, Renderer>
where where
Renderer: renderer::Renderer, Renderer: renderer::Renderer,
{ {
fn layout( fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
_translation: Vector,
) -> Node {
let limits = Limits::new(Size::ZERO, bounds); let limits = Limits::new(Size::ZERO, bounds);
let mut node = self let mut node = self
@ -276,7 +270,7 @@ where
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
self.element self.element
.as_widget_mut() .as_widget_mut()
.overlay(self.state, layout, renderer) .overlay(self.state, layout, renderer, Default::default())
} }
} }

View file

@ -4,15 +4,15 @@
use iced::{widget, Length}; use iced::{widget, Length};
use iced_core::text; use iced_core::text;
pub fn toggler<'a, Message, Theme: iced_widget::toggler::StyleSheet, Renderer>( pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>(
label: impl Into<Option<String>>,
is_checked: bool, is_checked: bool,
f: impl Fn(bool) -> Message + 'a, f: impl Fn(bool) -> Message + 'a,
) -> widget::Toggler<'a, Message, Theme, Renderer> ) -> widget::Toggler<'a, Message, Theme, Renderer>
where where
Renderer: iced_core::Renderer + text::Renderer, Renderer: iced_core::Renderer + text::Renderer,
{ {
widget::Toggler::new(label, is_checked, f) widget::Toggler::new(is_checked)
.on_toggle(f)
.size(24) .size(24)
.width(Length::Shrink) .width(Length::Shrink)
} }

View file

@ -41,9 +41,9 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> {
widget::row::with_capacity(2) widget::row::with_capacity(2)
.push(label) .push(label)
.push(close_button) .push(close_button)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.apply(widget::container) .apply(widget::container)
.style(theme::Container::custom(warning_container)) .class(theme::Container::custom(warning_container))
.padding(10) .padding(10)
.align_y(alignment::Vertical::Center) .align_y(alignment::Vertical::Center)
.width(Length::Fill) .width(Length::Fill)
@ -57,9 +57,9 @@ impl<'a, Message: 'static + Clone> From<Warning<'a, Message>> for Element<'a, Me
} }
#[must_use] #[must_use]
pub fn warning_container(theme: &Theme) -> widget::container::Appearance { pub fn warning_container(theme: &Theme) -> widget::container::Style {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
widget::container::Appearance { widget::container::Style {
icon_color: Some(theme.cosmic().warning.on.into()), icon_color: Some(theme.cosmic().warning.on.into()),
text_color: Some(theme.cosmic().warning.on.into()), text_color: Some(theme.cosmic().warning.on.into()),
background: Some(Background::Color(theme.cosmic().warning_color().into())), background: Some(Background::Color(theme.cosmic().warning_color().into())),