libcosmic updates
This commit is contained in:
parent
9c62f19e4b
commit
0491c4baaa
91 changed files with 3550 additions and 2300 deletions
32
Cargo.toml
32
Cargo.toml
|
|
@ -8,15 +8,22 @@ rust-version = "1.80"
|
|||
name = "cosmic"
|
||||
|
||||
[features]
|
||||
default = ["clipboard"]
|
||||
# Accessibility support
|
||||
a11y = ["iced/a11y", "iced_accessibility"]
|
||||
# Builds support for animated images
|
||||
animated-image = ["image", "dep:async-fs", "tokio?/io-util", "tokio?/fs"]
|
||||
# XXX Use "a11y"; which is causing a panic currently
|
||||
applet = ["wayland", "tokio", "cosmic-panel-config", "ron"]
|
||||
# XXX autosize should not be used on winit windows unless dialogs
|
||||
autosize = []
|
||||
applet = [
|
||||
"a11y",
|
||||
"autosize",
|
||||
"wayland",
|
||||
"tokio",
|
||||
"cosmic-panel-config",
|
||||
"ron",
|
||||
"multi-window",
|
||||
]
|
||||
applet-token = []
|
||||
clipboard = ["iced_sctk?/clipboard"]
|
||||
# Use the cosmic-settings-daemon for config handling
|
||||
dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"]
|
||||
# Debug features
|
||||
|
|
@ -61,7 +68,7 @@ wayland = [
|
|||
"ashpd?/wayland",
|
||||
"iced_runtime/wayland",
|
||||
"iced/wayland",
|
||||
"iced_sctk",
|
||||
"iced_winit/wayland",
|
||||
"cctk",
|
||||
]
|
||||
# multi-window support
|
||||
|
|
@ -120,7 +127,7 @@ path = "cosmic-theme"
|
|||
[dependencies.iced]
|
||||
path = "./iced"
|
||||
default-features = false
|
||||
features = ["advanced", "image", "lazy", "svg", "web-colors"]
|
||||
features = ["advanced", "image", "lazy", "svg", "web-colors", "tiny-skia"]
|
||||
|
||||
[dependencies.iced_runtime]
|
||||
path = "./iced/runtime"
|
||||
|
|
@ -146,13 +153,6 @@ optional = true
|
|||
[dependencies.iced_tiny_skia]
|
||||
path = "./iced/tiny_skia"
|
||||
|
||||
[dependencies.iced_style]
|
||||
path = "./iced/style"
|
||||
|
||||
[dependencies.iced_sctk]
|
||||
path = "./iced/sctk"
|
||||
optional = true
|
||||
|
||||
[dependencies.iced_winit]
|
||||
path = "./iced/winit"
|
||||
optional = true
|
||||
|
|
@ -189,3 +189,9 @@ dirs = "5.0.1"
|
|||
|
||||
[patch."https://github.com/pop-os/libcosmic"]
|
||||
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" }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ use std::ops::Deref;
|
|||
use crate::{CosmicConfigEntry, Update};
|
||||
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
|
||||
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>> {
|
||||
let conn = zbus::Connection::session().await?;
|
||||
|
|
@ -58,13 +61,20 @@ pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'stat
|
|||
config_id: &'static str,
|
||||
is_state: bool,
|
||||
) -> 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 {
|
||||
Changes(Changed),
|
||||
OwnerChanged(bool),
|
||||
}
|
||||
|
||||
let id = std::any::TypeId::of::<T>();
|
||||
iced_futures::subscription::channel((is_state, config_id, id), 5, move |mut tx| async move {
|
||||
stream::channel(5, move |mut tx| async move {
|
||||
let version = T::VERSION;
|
||||
|
||||
let Ok(cosmic_config) = (if is_state {
|
||||
|
|
|
|||
|
|
@ -384,6 +384,7 @@ where
|
|||
) -> (Vec<crate::Error>, Vec<&'static str>);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Update<T> {
|
||||
pub errors: Vec<crate::Error>,
|
||||
pub keys: Vec<&'static str>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use iced_futures::futures::SinkExt;
|
||||
use iced_futures::{futures::channel::mpsc, subscription};
|
||||
use iced_futures::futures::{SinkExt, Stream};
|
||||
use iced_futures::{futures::channel::mpsc, stream};
|
||||
use notify::RecommendedWatcher;
|
||||
use std::{borrow::Cow, hash::Hash};
|
||||
|
||||
|
|
@ -24,17 +24,7 @@ pub fn config_subscription<
|
|||
config_id: Cow<'static, str>,
|
||||
config_version: u64,
|
||||
) -> iced_futures::Subscription<crate::Update<T>> {
|
||||
subscription::channel(id, 100, move |mut output| {
|
||||
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;
|
||||
}
|
||||
}
|
||||
})
|
||||
iced_futures::Subscription::run_with_id(id, watcher_stream(config_id, config_version, false))
|
||||
}
|
||||
|
||||
pub fn config_state_subscription<
|
||||
|
|
@ -45,26 +35,30 @@ pub fn config_state_subscription<
|
|||
config_id: Cow<'static, str>,
|
||||
config_version: u64,
|
||||
) -> 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();
|
||||
async move {
|
||||
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 {
|
||||
state = start_listening(state, &mut output, id).await;
|
||||
state = start_listening::<T>(state, &mut output).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn start_listening<
|
||||
I: Copy,
|
||||
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
||||
>(
|
||||
async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry>(
|
||||
state: ConfigState<T>,
|
||||
output: &mut mpsc::Sender<crate::Update<T>>,
|
||||
id: I,
|
||||
) -> ConfigState<T> {
|
||||
use iced_futures::futures::{future::pending, StreamExt};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,20 @@ edition = "2021"
|
|||
once_cell = "1"
|
||||
rust-embed = "8.0.0"
|
||||
tracing = "0.1"
|
||||
env_logger = "0.10.0"
|
||||
log = "0.4.17"
|
||||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
default-features = false
|
||||
features = ["applet", "tokio", "wayland"]
|
||||
features = [
|
||||
"applet",
|
||||
"applet-token",
|
||||
"multi-window",
|
||||
"tokio",
|
||||
"wayland",
|
||||
"winit",
|
||||
"desktop",
|
||||
"dbus-config",
|
||||
"image",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,5 +3,10 @@ use crate::window::Window;
|
|||
mod window;
|
||||
|
||||
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>(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
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::{Command, Limits};
|
||||
use cosmic::iced::{Length, Limits, Task};
|
||||
use cosmic::iced_runtime::core::window;
|
||||
use cosmic::iced_style::application;
|
||||
use cosmic::theme::iced;
|
||||
use cosmic::widget::{list_column, settings, toggler};
|
||||
use cosmic::{Element, Theme};
|
||||
|
||||
|
|
@ -37,22 +38,19 @@ impl cosmic::Application for Window {
|
|||
&mut self.core
|
||||
}
|
||||
|
||||
fn init(
|
||||
core: Core,
|
||||
_flags: Self::Flags,
|
||||
) -> (Self, Command<cosmic::app::Message<Self::Message>>) {
|
||||
fn init(core: Core, _flags: Self::Flags) -> (Self, Task<cosmic::app::Message<Self::Message>>) {
|
||||
let window = Window {
|
||||
core,
|
||||
..Default::default()
|
||||
};
|
||||
(window, Command::none())
|
||||
(window, Task::none())
|
||||
}
|
||||
|
||||
fn on_close_requested(&self, id: window::Id) -> Option<Message> {
|
||||
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 {
|
||||
Message::TogglePopup => {
|
||||
return if let Some(p) = self.popup.take() {
|
||||
|
|
@ -60,17 +58,23 @@ impl cosmic::Application for Window {
|
|||
} else {
|
||||
let new_id = Id::unique();
|
||||
self.popup.replace(new_id);
|
||||
let mut popup_settings =
|
||||
self.core
|
||||
.applet
|
||||
.get_popup_settings(Id::MAIN, new_id, None, None, None);
|
||||
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||
self.core.main_window_id().unwrap(),
|
||||
new_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
popup_settings.positioner.size_limits = Limits::NONE
|
||||
.max_width(372.0)
|
||||
.min_width(300.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)
|
||||
}
|
||||
};
|
||||
}
|
||||
Message::PopupClosed(id) => {
|
||||
if self.popup.as_ref() == Some(&id) {
|
||||
|
|
@ -79,7 +83,7 @@ impl cosmic::Application for Window {
|
|||
}
|
||||
Message::ToggleExampleRow(toggled) => self.example_row = toggled,
|
||||
}
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Self::Message> {
|
||||
|
|
@ -93,15 +97,16 @@ impl cosmic::Application for Window {
|
|||
fn view_window(&self, _id: Id) -> Element<Self::Message> {
|
||||
let content_list = list_column().padding(5).spacing(0).add(settings::item(
|
||||
"Example row",
|
||||
toggler(None, self.example_row, |value| {
|
||||
cosmic::widget::container(toggler(self.example_row, |value| {
|
||||
Message::ToggleExampleRow(value)
|
||||
}),
|
||||
}))
|
||||
.height(Length::Fixed(50.)),
|
||||
));
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,15 @@ tracing-log = "0.2.0"
|
|||
[dependencies.libcosmic]
|
||||
path = "../../"
|
||||
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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
//! 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::widget::nav_bar;
|
||||
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`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {}
|
||||
pub enum Message {
|
||||
Input1(String),
|
||||
Input2(String),
|
||||
}
|
||||
|
||||
/// The [`App`] stores application-specific state.
|
||||
pub struct App {
|
||||
core: Core,
|
||||
nav_model: nav_bar::Model,
|
||||
input_1: String,
|
||||
input_2: String,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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();
|
||||
|
||||
for (title, content) in input {
|
||||
|
|
@ -90,7 +96,12 @@ impl cosmic::Application for App {
|
|||
|
||||
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();
|
||||
|
||||
|
|
@ -103,14 +114,22 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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.update_title()
|
||||
}
|
||||
|
||||
/// Handle application events here.
|
||||
fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
||||
match message {
|
||||
Message::Input1(v) => {
|
||||
self.input_1 = v;
|
||||
}
|
||||
Message::Input2(v) => {
|
||||
self.input_2 = v;
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -122,11 +141,20 @@ impl cosmic::Application for App {
|
|||
|
||||
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)
|
||||
.height(iced::Length::Shrink)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.align_y(iced::alignment::Vertical::Center);
|
||||
.align_x(iced::alignment::Horizontal::Center),
|
||||
)
|
||||
.width(iced::Length::Fill)
|
||||
.height(iced::Length::Shrink)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.align_y(iced::alignment::Vertical::Center);
|
||||
|
||||
Element::from(centered)
|
||||
}
|
||||
|
|
@ -142,10 +170,14 @@ where
|
|||
.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 window_title = format!("{header_title} — COSMIC AppDemo");
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! Calendar widget example
|
||||
|
||||
use chrono::{Local, NaiveDate};
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Core, Settings, Task};
|
||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||
|
||||
/// Runs application with these settings
|
||||
|
|
@ -50,7 +50,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 mut app = App {
|
||||
|
|
@ -64,7 +64,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
Message::DateSelected(date) => {
|
||||
self.date_selected = date;
|
||||
|
|
@ -73,7 +73,7 @@ impl cosmic::Application for App {
|
|||
|
||||
println!("Date selected: {:?}", self.date_selected);
|
||||
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -99,7 +99,7 @@ impl App
|
|||
where
|
||||
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_window_title(String::from("Calendar Demo"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
//! Application API example
|
||||
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Task, Core, Settings};
|
||||
use cosmic::iced_core::Size;
|
||||
use cosmic::widget::{menu, segmented_button};
|
||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||
|
|
@ -65,7 +65,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
core,
|
||||
button_label: String::from("Right click me"),
|
||||
|
|
@ -80,10 +80,10 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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:?}");
|
||||
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use cosmic::{
|
|||
ThemeBuilder,
|
||||
},
|
||||
font::load_fonts,
|
||||
iced::{self, Application, Command, Length, Subscription},
|
||||
iced::{self, Application, Length, Subscription, Task},
|
||||
iced::{
|
||||
subscription,
|
||||
widget::{self, column, container, horizontal_space, row, text},
|
||||
|
|
@ -324,7 +324,7 @@ impl Application for Window {
|
|||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
|
||||
fn new(_flags: ()) -> (Self, Task<Self::Message>) {
|
||||
let mut window = Window::default()
|
||||
.nav_bar_toggled(true)
|
||||
.show_maximize(true)
|
||||
|
|
@ -389,8 +389,8 @@ impl Application for Window {
|
|||
])
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||
let mut ret = Command::none();
|
||||
fn update(&mut self, message: Message) -> iced::Task<Self::Message> {
|
||||
let mut ret = Task::none();
|
||||
match message {
|
||||
Message::NavBar(key) => {
|
||||
if let Some(page) = self.nav_id_to_page.get(key).copied() {
|
||||
|
|
@ -437,10 +437,10 @@ impl Application for Window {
|
|||
Message::ToggleNavBarCondensed => {
|
||||
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed
|
||||
}
|
||||
Message::Drag => return drag(window::Id::MAIN),
|
||||
Message::Close => return close(window::Id::MAIN),
|
||||
Message::Minimize => return minimize(window::Id::MAIN, true),
|
||||
Message::Maximize => return toggle_maximize(window::Id::MAIN),
|
||||
Message::Drag => return drag(self.core.main_window_id().unwrap()),
|
||||
Message::Close => return close(self.core.main_window_id().unwrap()),
|
||||
Message::Minimize => return minimize(self.core.main_window_id().unwrap(), true),
|
||||
Message::Maximize => return toggle_maximize(self.core.main_window_id().unwrap()),
|
||||
|
||||
Message::InputChanged => {}
|
||||
|
||||
|
|
|
|||
|
|
@ -482,7 +482,7 @@ impl State {
|
|||
))
|
||||
.layer(cosmic::cosmic_theme::Layer::Secondary)
|
||||
.padding(16)
|
||||
.style(cosmic::theme::Container::Background)
|
||||
.class(cosmic::theme::Container::Background)
|
||||
.into(),
|
||||
cosmic::widget::text_input::secure_input(
|
||||
"Type to search apps or type “?” for more options...",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
//! Application API example
|
||||
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Task, Core, Settings};
|
||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||
|
||||
/// Runs application with these settings
|
||||
|
|
@ -51,7 +51,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
core,
|
||||
selected: 0,
|
||||
|
|
@ -67,7 +67,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
Message::Clicked(id) => self.selected = id,
|
||||
Message::Remove(id) => {
|
||||
|
|
@ -75,7 +75,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -106,7 +106,7 @@ impl App
|
|||
where
|
||||
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_window_title(String::from("Image Button Demo"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{env, process};
|
||||
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Task, Core, Settings};
|
||||
use cosmic::iced::window;
|
||||
use cosmic::iced_core::alignment::{Horizontal, Vertical};
|
||||
use cosmic::iced_core::keyboard::Key;
|
||||
|
|
@ -97,7 +97,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
core,
|
||||
config: Config {
|
||||
|
|
@ -106,7 +106,7 @@ impl cosmic::Application for App {
|
|||
key_binds: key_binds(),
|
||||
};
|
||||
|
||||
(app, Command::none())
|
||||
(app, Task::none())
|
||||
}
|
||||
|
||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||
|
|
@ -114,13 +114,13 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
Message::WindowClose => {
|
||||
return window::close(window::Id::MAIN);
|
||||
return window::close(self.core.main_window_id().unwrap());
|
||||
}
|
||||
Message::WindowNew => match env::current_exe() {
|
||||
Ok(exe) => match process::Command::new(&exe).spawn() {
|
||||
Ok(exe) => match process::Task::new(&exe).spawn() {
|
||||
Ok(_child) => {}
|
||||
Err(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,
|
||||
}
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use cosmic::{
|
|||
iced_core::{id, Alignment, Length, Point},
|
||||
iced_widget::{column, container, scrollable, text, text_input},
|
||||
widget::{button, header_bar},
|
||||
ApplicationExt, Command,
|
||||
ApplicationExt, Task,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -42,10 +42,10 @@ impl cosmic::Application for MultiWindow {
|
|||
&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 {
|
||||
windows: HashMap::from([(
|
||||
window::Id::MAIN,
|
||||
self.core.main_window_id().unwrap(),
|
||||
Window {
|
||||
input_id: id::Id::new("main"),
|
||||
input_value: String::new(),
|
||||
|
|
@ -54,12 +54,12 @@ impl cosmic::Application for MultiWindow {
|
|||
core,
|
||||
};
|
||||
|
||||
(windows, cosmic::app::Command::none())
|
||||
(windows, cosmic::app::Task::none())
|
||||
}
|
||||
|
||||
fn subscription(&self) -> cosmic::iced_futures::Subscription<Self::Message> {
|
||||
event::listen_with(|event, _| {
|
||||
if let iced::Event::Window(id, window_event) = event {
|
||||
event::listen_with(|event, _, id| {
|
||||
if let iced::Event::Window(window_event) = event {
|
||||
match window_event {
|
||||
window::Event::CloseRequested => Some(Message::CloseWindow(id)),
|
||||
window::Event::Opened { position, .. } => {
|
||||
|
|
@ -77,18 +77,18 @@ impl cosmic::Application for MultiWindow {
|
|||
fn update(
|
||||
&mut self,
|
||||
message: Self::Message,
|
||||
) -> iced::Command<cosmic::app::Message<Self::Message>> {
|
||||
) -> iced::Task<cosmic::app::Message<Self::Message>> {
|
||||
match message {
|
||||
Message::CloseWindow(id) => window::close(id),
|
||||
Message::WindowClosed(id) => {
|
||||
self.windows.remove(&id);
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
Message::WindowOpened(id, ..) => {
|
||||
if let Some(window) = self.windows.get(&id) {
|
||||
text_input::focus(window.input_id.clone())
|
||||
} else {
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::NewWindow => {
|
||||
|
|
@ -113,13 +113,13 @@ impl cosmic::Application for MultiWindow {
|
|||
spawn_window
|
||||
}
|
||||
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 {
|
||||
w.input_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,17 +142,15 @@ impl cosmic::Application for MultiWindow {
|
|||
column![input, new_window_button]
|
||||
.spacing(50)
|
||||
.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)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y();
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill);
|
||||
|
||||
if id == window::Id::MAIN {
|
||||
if id == self.core.main_window_id().unwrap() {
|
||||
window_content.into()
|
||||
} else {
|
||||
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> {
|
||||
self.view_window(window::Id::MAIN)
|
||||
self.view_window(self.core.main_window_id().unwrap())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Core, Settings, Task};
|
||||
use cosmic::iced_core::Size;
|
||||
use cosmic::widget::{menu, nav_bar};
|
||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||
|
|
@ -106,7 +106,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
for (title, content) in input {
|
||||
|
|
@ -143,13 +143,13 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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.update_title()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Message::NavMenuAction(message) => match message {
|
||||
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.
|
||||
|
|
@ -200,7 +200,7 @@ where
|
|||
.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 window_title = format!("{header_title} — COSMIC AppDemo");
|
||||
self.set_header_title(header_title);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! An application which provides an open dialog
|
||||
|
||||
use apply::Apply;
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Task, Core, Settings};
|
||||
use cosmic::dialog::file_chooser::{self, FileFilter};
|
||||
use cosmic::iced_core::Length;
|
||||
use cosmic::widget::button;
|
||||
|
|
@ -66,7 +66,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
core,
|
||||
file_contents: String::new(),
|
||||
|
|
@ -77,7 +77,7 @@ impl cosmic::Application for App {
|
|||
app.set_header_title("Open a file".into());
|
||||
let cmd = app.set_window_title(
|
||||
"COSMIC OpenDialog Demo".into(),
|
||||
cosmic::iced::window::Id::MAIN,
|
||||
cosmic::iced::self.core.main_window_id().unwrap(),
|
||||
);
|
||||
|
||||
(app, cmd)
|
||||
|
|
@ -88,7 +88,7 @@ impl cosmic::Application for App {
|
|||
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 {
|
||||
Message::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> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
//! Application API example
|
||||
|
||||
use cosmic::app::{Command, Core, Settings};
|
||||
use cosmic::app::{Core, Settings, Task};
|
||||
use cosmic::{executor, iced, ApplicationExt, Element};
|
||||
|
||||
/// Runs application with these settings
|
||||
|
|
@ -55,7 +55,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
core,
|
||||
editing: false,
|
||||
|
|
@ -63,7 +63,7 @@ impl cosmic::Application for App {
|
|||
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()),
|
||||
app.update_title(),
|
||||
]);
|
||||
|
|
@ -72,7 +72,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
Message::Input(text) => {
|
||||
self.input = text;
|
||||
|
|
@ -83,7 +83,7 @@ impl cosmic::Application for App {
|
|||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -115,7 +115,7 @@ impl App
|
|||
where
|
||||
Self: cosmic::Application,
|
||||
{
|
||||
fn update_title(&mut self) -> Command<Message> {
|
||||
fn update_title(&mut self) -> Task<Message> {
|
||||
let window_title = format!("COSMIC TextInputs Demo");
|
||||
self.set_header_title(window_title.clone());
|
||||
self.set_window_title(window_title)
|
||||
|
|
|
|||
2
iced
2
iced
|
|
@ -1 +1 @@
|
|||
Subproject commit 061995084a5775b4fd7df63dda336be01ddf491c
|
||||
Subproject commit f2f9dfc6c37e14c4f8ff5dafe70d72d3c40eb692
|
||||
|
|
@ -7,62 +7,88 @@ use iced::window;
|
|||
use super::Message;
|
||||
|
||||
/// 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`].
|
||||
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)
|
||||
}
|
||||
|
||||
/// Convenience methods for building message-based commands.
|
||||
pub mod 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))
|
||||
}
|
||||
|
||||
/// Creates a command which yields a cosmic message.
|
||||
pub fn cosmic<M: Send + 'static>(
|
||||
message: crate::app::cosmic::Message,
|
||||
) -> crate::app::Command<M> {
|
||||
pub fn cosmic<M: Send + 'static>(message: crate::app::cosmic::Message) -> crate::app::Task<M> {
|
||||
super::message(super::Message::Cosmic(message))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag<M: Send + 'static>(id: Option<window::Id>) -> iced::Command<Message<M>> {
|
||||
crate::command::drag(id).map(Message::Cosmic)
|
||||
impl crate::app::Core {
|
||||
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>(
|
||||
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>> {
|
||||
pub fn set_theme<M: Send + 'static>(theme: crate::Theme) -> iced::Task<Message<M>> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{cell::OnceCell, collections::HashMap};
|
||||
|
||||
use crate::widget::nav_bar;
|
||||
use cosmic_config::CosmicConfigEntry;
|
||||
|
|
@ -40,8 +40,8 @@ pub struct Window {
|
|||
pub show_close: bool,
|
||||
pub show_maximize: bool,
|
||||
pub show_minimize: bool,
|
||||
height: u32,
|
||||
width: u32,
|
||||
height: f32,
|
||||
width: f32,
|
||||
}
|
||||
|
||||
/// COSMIC-specific application settings
|
||||
|
|
@ -93,6 +93,10 @@ pub struct Core {
|
|||
|
||||
#[cfg(feature = "dbus-config")]
|
||||
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 {
|
||||
|
|
@ -135,10 +139,10 @@ impl Default for Core {
|
|||
show_maximize: true,
|
||||
show_minimize: true,
|
||||
show_window_menu: false,
|
||||
height: 0,
|
||||
width: 0,
|
||||
height: 0.,
|
||||
width: 0.,
|
||||
},
|
||||
focused_window: Some(window::Id::MAIN),
|
||||
focused_window: None,
|
||||
#[cfg(feature = "applet")]
|
||||
applet: crate::applet::Context::default(),
|
||||
#[cfg(feature = "single-instance")]
|
||||
|
|
@ -148,6 +152,8 @@ impl Default for Core {
|
|||
portal_is_dark: None,
|
||||
portal_accent: 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.
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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.is_condensed_update();
|
||||
}
|
||||
|
|
@ -364,4 +370,12 @@ impl Core {
|
|||
self.portal_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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,23 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{command, Application, ApplicationExt, Core, Subscription};
|
||||
use super::{Application, ApplicationExt, Core, Subscription};
|
||||
use crate::config::CosmicTk;
|
||||
use crate::theme::{self, Theme, ThemeType, THEME};
|
||||
use crate::theme::{Theme, ThemeType, THEME};
|
||||
use crate::widget::nav_bar;
|
||||
use crate::{keyboard_nav, Element};
|
||||
#[cfg(feature = "wayland")]
|
||||
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
|
||||
use cosmic_theme::ThemeMode;
|
||||
#[cfg(feature = "wayland")]
|
||||
use iced::event::wayland::{self, WindowEvent};
|
||||
#[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;
|
||||
use iced::event::wayland;
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
use iced::Application as IcedApplication;
|
||||
use iced::{window, Command};
|
||||
use iced::{window, Task};
|
||||
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;
|
||||
|
||||
/// A message managed internally by COSMIC.
|
||||
|
|
@ -65,7 +56,7 @@ pub enum Message {
|
|||
/// Updates the window maximized state
|
||||
WindowMaximized(window::Id, bool),
|
||||
/// Updates the tracked window geometry.
|
||||
WindowResize(window::Id, u32, u32),
|
||||
WindowResize(window::Id, f32, f32),
|
||||
/// Tracks updates to window state.
|
||||
#[cfg(feature = "wayland")]
|
||||
WindowState(window::Id, WindowState),
|
||||
|
|
@ -86,7 +77,9 @@ pub enum Message {
|
|||
Unfocus(window::Id),
|
||||
/// Tracks updates to window suggested size.
|
||||
#[cfg(feature = "applet")]
|
||||
Configure(cctk::sctk::shell::xdg::window::WindowConfigure),
|
||||
SuggestedBounds(Option<iced::Size>),
|
||||
/// Window Created
|
||||
MainWindowCreated(window::Id),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -94,109 +87,104 @@ pub struct Cosmic<App> {
|
|||
pub app: App,
|
||||
}
|
||||
|
||||
impl<T: Application> IcedApplication for Cosmic<T>
|
||||
impl<T: Application> Cosmic<T>
|
||||
where
|
||||
T::Message: Send + 'static,
|
||||
{
|
||||
type Executor = T::Executor;
|
||||
type Flags = (Core, T::Flags);
|
||||
type Message = super::Message<T::Message>;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new((mut core, flags): Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||
pub fn init(
|
||||
(mut core, flags, window_settings): (Core, T::Flags, iced::window::Settings),
|
||||
) -> (Self, iced::Task<super::Message<T::Message>>) {
|
||||
#[cfg(feature = "dbus-config")]
|
||||
{
|
||||
use iced_futures::futures::executor::block_on;
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
fn title(&self) -> String {
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
pub fn title(&self) -> String {
|
||||
self.app.title().to_string()
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
fn title(&self, id: window::Id) -> String {
|
||||
#[cfg(feature = "multi-window")]
|
||||
pub fn title(&self, id: window::Id) -> 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 {
|
||||
super::Message::App(message) => self.app.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")]
|
||||
super::Message::DbusActivation(message) => self.app.dbus_activation(message),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
fn scale_factor(&self) -> f64 {
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
f64::from(self.app.core().scale_factor())
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
fn scale_factor(&self, _id: window::Id) -> f64 {
|
||||
#[cfg(feature = "multi-window")]
|
||||
pub fn scale_factor(&self, _id: window::Id) -> f64 {
|
||||
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() {
|
||||
style
|
||||
} else if self.app.core().window.sharp_corners {
|
||||
theme::Application::default()
|
||||
let theme = THEME.lock().unwrap();
|
||||
crate::style::iced::application::appearance(theme.borrow())
|
||||
} 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,
|
||||
icon_color: theme.cosmic().on_bg_color().into(),
|
||||
text_color: theme.cosmic().on_bg_color().into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
let window_events = listen_with(|event, _| {
|
||||
pub fn subscription(&self) -> Subscription<super::Message<T::Message>> {
|
||||
let window_events = listen_with(|event, _, id| {
|
||||
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));
|
||||
}
|
||||
iced::Event::Window(id, window::Event::Closed) => {
|
||||
iced::Event::Window(window::Event::Closed) => {
|
||||
return Some(Message::SurfaceClosed(id))
|
||||
}
|
||||
iced::Event::Window(id, window::Event::Focused) => return Some(Message::Focus(id)),
|
||||
iced::Event::Window(id, window::Event::Unfocused) => {
|
||||
return Some(Message::Unfocus(id))
|
||||
iced::Event::Window(window::Event::Focused) => return Some(Message::Focus(id)),
|
||||
iced::Event::Window(window::Event::Unfocused) => return Some(Message::Unfocus(id)),
|
||||
iced::Event::Window(window::Event::Opened { .. }) => {
|
||||
return Some(Message::MainWindowCreated(id));
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
iced::Event::PlatformSpecific(PlatformSpecific::Wayland(event)) => match event {
|
||||
wayland::Event::Window(WindowEvent::State(state), _surface, id) => {
|
||||
return Some(Message::WindowState(id, state));
|
||||
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
|
||||
match event {
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
fn theme(&self) -> Self::Theme {
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
pub fn theme(&self) -> Theme {
|
||||
crate::theme::active()
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
fn theme(&self, _id: window::Id) -> Self::Theme {
|
||||
#[cfg(feature = "multi-window")]
|
||||
pub fn theme(&self, _id: window::Id) -> Theme {
|
||||
crate::theme::active()
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
fn view(&self, id: window::Id) -> Element<Self::Message> {
|
||||
if id != self.app.main_window_id() {
|
||||
#[cfg(feature = "multi-window")]
|
||||
pub fn view(&self, id: window::Id) -> Element<super::Message<T::Message>> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -298,37 +291,43 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
fn view(&self) -> Element<Self::Message> {
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
pub fn view(&self) -> Element<super::Message<T::Message>> {
|
||||
self.app.view_main()
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
pub fn close(&mut self) -> iced::Command<super::Message<T::Message>> {
|
||||
iced::Command::single(Action::Window(WindowAction::Close(
|
||||
self.app.main_window_id(),
|
||||
)))
|
||||
pub fn close(&mut self) -> iced::Task<super::Message<T::Message>> {
|
||||
if let Some(id) = self.app.core().main_window_id() {
|
||||
iced::window::close(id)
|
||||
} else {
|
||||
iced::Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_height(height);
|
||||
}
|
||||
|
|
@ -336,15 +335,19 @@ impl<T: Application> Cosmic<T> {
|
|||
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)
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
return iced::window::fetch_maximized(id, move |maximized| {
|
||||
return iced::window::get_maximized(id).map(move |maximized| {
|
||||
super::Message::Cosmic(Message::WindowMaximized(id, maximized))
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
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(
|
||||
WindowState::MAXIMIZED
|
||||
| WindowState::FULLSCREEN
|
||||
|
|
@ -359,7 +362,12 @@ impl<T: Application> Cosmic<T> {
|
|||
|
||||
#[cfg(feature = "wayland")]
|
||||
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 =
|
||||
capabilities.contains(WindowManagerCapabilities::MAXIMIZE);
|
||||
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::Search => return self.app.on_search(),
|
||||
|
||||
keyboard_nav::Message::Fullscreen => {
|
||||
return command::toggle_maximize(Some(self.app.main_window_id()))
|
||||
}
|
||||
keyboard_nav::Message::Fullscreen => return self.app.core().toggle_maximize(None),
|
||||
},
|
||||
|
||||
Message::ContextDrawer(show) => {
|
||||
|
|
@ -389,11 +395,11 @@ impl<T: Application> Cosmic<T> {
|
|||
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) => {
|
||||
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();
|
||||
// Ignore updates if the current theme mode does not match.
|
||||
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());
|
||||
// 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) => {
|
||||
if !keys.contains(&"is_dark") {
|
||||
return iced::Command::none();
|
||||
return iced::Task::none();
|
||||
}
|
||||
if match THEME.lock().unwrap().theme_type {
|
||||
ThemeType::System {
|
||||
|
|
@ -488,7 +494,7 @@ impl<T: Application> Cosmic<T> {
|
|||
} => prefer_dark.is_some(),
|
||||
_ => false,
|
||||
} {
|
||||
return iced::Command::none();
|
||||
return iced::Task::none();
|
||||
}
|
||||
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")]
|
||||
return iced_sctk::commands::activation::activate(
|
||||
self.app.main_window_id(),
|
||||
#[allow(clippy::used_underscore_binding)]
|
||||
_token,
|
||||
);
|
||||
}
|
||||
Message::SurfaceClosed(id) => {
|
||||
if let Some(msg) = self.app.on_close_requested(id) {
|
||||
return self.app.update(msg);
|
||||
if let Some(id) = self.app.core().main_window_id() {
|
||||
return iced_winit::platform_specific::commands::activation::activate(
|
||||
id,
|
||||
#[allow(clippy::used_underscore_binding)]
|
||||
_token,
|
||||
);
|
||||
}
|
||||
}
|
||||
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 => {
|
||||
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")]
|
||||
Message::DesktopSettings(crate::theme::portal::Desktop::ColorScheme(s)) => {
|
||||
|
|
@ -555,7 +575,7 @@ impl<T: Application> Cosmic<T> {
|
|||
} => prefer_dark.is_some(),
|
||||
_ => false,
|
||||
} {
|
||||
return iced::Command::none();
|
||||
return iced::Task::none();
|
||||
}
|
||||
let is_dark = match s {
|
||||
ColorScheme::NoPreference => None,
|
||||
|
|
@ -595,7 +615,7 @@ impl<T: Application> Cosmic<T> {
|
|||
|
||||
if cur_accent.distance_squared(*c) < 0.00001 {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "applet")]
|
||||
Message::Configure(configure) => {
|
||||
if let Some(w) = configure.new_size.0 {
|
||||
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);
|
||||
Message::MainWindowCreated(id) => {
|
||||
let core = self.app.core_mut();
|
||||
_ = core.main_window.set(id);
|
||||
}
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
290
src/app/mod.rs
290
src/app/mod.rs
|
|
@ -9,6 +9,8 @@
|
|||
pub mod command;
|
||||
mod core;
|
||||
pub mod cosmic;
|
||||
#[cfg(all(feature = "winit", feature = "multi-window"))]
|
||||
pub(crate) mod multi_window;
|
||||
pub mod settings;
|
||||
|
||||
pub mod message {
|
||||
|
|
@ -47,17 +49,14 @@ pub mod message {
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use self::command::Command;
|
||||
pub use self::command::Task;
|
||||
pub use self::core::Core;
|
||||
pub use self::settings::Settings;
|
||||
use crate::prelude::*;
|
||||
use crate::theme::THEME;
|
||||
use crate::widget::{context_drawer, horizontal_space, id_container, menu, nav_bar, popover};
|
||||
use apply::Apply;
|
||||
#[cfg(all(feature = "winit", feature = "multi-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::window;
|
||||
use iced::{Length, Subscription};
|
||||
pub use message::Message;
|
||||
use url::Url;
|
||||
|
|
@ -73,15 +72,15 @@ use {
|
|||
pub(crate) fn iced_settings<App: Application>(
|
||||
settings: Settings,
|
||||
flags: App::Flags,
|
||||
) -> iced::Settings<(Core, App::Flags)> {
|
||||
) -> (iced::Settings, (Core, App::Flags, iced::window::Settings)) {
|
||||
preload_fonts();
|
||||
|
||||
let mut core = Core::default();
|
||||
core.debug = settings.debug;
|
||||
core.icon_theme_override = settings.default_icon_theme.is_some();
|
||||
core.set_scale_factor(settings.scale_factor);
|
||||
core.set_window_width(settings.size.width as u32);
|
||||
core.set_window_height(settings.size.height as u32);
|
||||
core.set_window_width(settings.size.width);
|
||||
core.set_window_height(settings.size.height);
|
||||
|
||||
if let Some(icon_theme) = settings.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);
|
||||
|
||||
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.default_font = settings.default_font;
|
||||
iced.default_text_size = iced::Pixels(settings.default_text_size);
|
||||
iced.exit_on_close_request = settings.exit_on_close;
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
{
|
||||
let exit_on_close = settings.exit_on_close;
|
||||
iced.window.exit_on_close_request = exit_on_close;
|
||||
}
|
||||
let exit_on_close = settings.exit_on_close;
|
||||
iced.exit_on_close_request = exit_on_close;
|
||||
let mut window_settings = iced::window::Settings::default();
|
||||
window_settings.exit_on_close_request = exit_on_close;
|
||||
iced.id = Some(App::APP_ID.to_owned());
|
||||
#[cfg(all(not(feature = "wayland"), target_os = "linux"))]
|
||||
{
|
||||
iced.window.platform_specific.application_id = App::APP_ID.to_string();
|
||||
window_settings.platform_specific.application_id = App::APP_ID.to_string();
|
||||
core.exit_on_main_window_closed = exit_on_close;
|
||||
|
||||
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")]
|
||||
{
|
||||
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
|
||||
window_settings.transparent = settings.transparent;
|
||||
(iced, (core, flags, window_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.
|
||||
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
||||
let settings = iced_settings::<App>(settings, flags);
|
||||
|
||||
cosmic::Cosmic::<App>::run(settings)
|
||||
let default_font = settings.default_font;
|
||||
let (settings, flags) = iced_settings::<App>(settings, flags);
|
||||
#[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")]
|
||||
|
|
@ -362,9 +367,41 @@ where
|
|||
tracing::info!("Another instance is running");
|
||||
Ok(())
|
||||
} else {
|
||||
let mut settings = iced_settings::<App>(settings, flags);
|
||||
settings.flags.0.single_instance = true;
|
||||
cosmic::Cosmic::<App>::run(settings)
|
||||
let (settings, mut flags) = iced_settings::<App>(settings, flags);
|
||||
flags.0.single_instance = true;
|
||||
|
||||
#[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;
|
||||
|
||||
/// 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`.
|
||||
fn context_drawer(&self) -> Option<Element<Self::Message>> {
|
||||
|
|
@ -441,11 +478,6 @@ where
|
|||
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.
|
||||
fn nav_bar(&self) -> Option<Element<Message<Self::Message>>> {
|
||||
if !self.core().nav_bar_active() {
|
||||
|
|
@ -491,32 +523,32 @@ where
|
|||
}
|
||||
|
||||
// Called when context drawer is toggled
|
||||
fn on_context_drawer(&mut self) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn on_context_drawer(&mut self) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Called when the escape key is pressed.
|
||||
fn on_escape(&mut self) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn on_escape(&mut self) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Called when a navigation item is selected.
|
||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Called when a context menu is requested for a navigation item.
|
||||
fn on_nav_context(&mut self, id: nav_bar::Id) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn on_nav_context(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Called when the search function is requested.
|
||||
fn on_search(&mut self) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn on_search(&mut self) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
|
|
@ -524,8 +556,8 @@ where
|
|||
}
|
||||
|
||||
/// Respond to an application-specific message.
|
||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Respond to a system theme change
|
||||
|
|
@ -533,8 +565,8 @@ where
|
|||
&mut self,
|
||||
keys: &[&'static str],
|
||||
new_theme: &cosmic_theme::Theme,
|
||||
) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Respond to a system theme mode change
|
||||
|
|
@ -542,8 +574,8 @@ where
|
|||
&mut self,
|
||||
keys: &[&'static str],
|
||||
new_theme: &cosmic_theme::ThemeMode,
|
||||
) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Constructs the view for the main window.
|
||||
|
|
@ -555,33 +587,33 @@ where
|
|||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Handles dbus activation messages
|
||||
#[cfg(feature = "single-instance")]
|
||||
fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Command<Self::Message> {
|
||||
Command::none()
|
||||
fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Task<Self::Message> {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods automatically derived for all types implementing [`Application`].
|
||||
pub trait ApplicationExt: Application {
|
||||
/// Initiates a window drag.
|
||||
fn drag(&mut self) -> Command<Self::Message>;
|
||||
fn drag(&mut self) -> Task<Self::Message>;
|
||||
|
||||
/// Maximizes the window.
|
||||
fn maximize(&mut self) -> Command<Self::Message>;
|
||||
fn maximize(&mut self) -> Task<Self::Message>;
|
||||
|
||||
/// Minimizes the window.
|
||||
fn minimize(&mut self) -> Command<Self::Message>;
|
||||
fn minimize(&mut self) -> Task<Self::Message>;
|
||||
/// Get the title of the main window.
|
||||
|
||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
fn title(&self) -> &str;
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
#[cfg(feature = "multi-window")]
|
||||
/// Get the title of a window.
|
||||
fn title(&self, id: window::Id) -> &str;
|
||||
|
||||
|
|
@ -600,56 +632,58 @@ pub trait ApplicationExt: Application {
|
|||
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.
|
||||
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.
|
||||
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.
|
||||
fn view_main(&self) -> Element<Message<Self::Message>>;
|
||||
}
|
||||
|
||||
impl<App: Application> ApplicationExt for App {
|
||||
fn drag(&mut self) -> Command<Self::Message> {
|
||||
command::drag(Some(self.main_window_id()))
|
||||
fn drag(&mut self) -> Task<Self::Message> {
|
||||
self.core().drag(None)
|
||||
}
|
||||
|
||||
fn maximize(&mut self) -> Command<Self::Message> {
|
||||
command::maximize(Some(self.main_window_id()), true)
|
||||
fn maximize(&mut self) -> Task<Self::Message> {
|
||||
self.core().maximize(None, true)
|
||||
}
|
||||
|
||||
fn minimize(&mut self) -> Command<Self::Message> {
|
||||
command::minimize(Some(self.main_window_id()))
|
||||
fn minimize(&mut self) -> Task<Self::Message> {
|
||||
self.core().minimize(None)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
#[cfg(feature = "multi-window")]
|
||||
fn title(&self, id: window::Id) -> &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 {
|
||||
self.core()
|
||||
.title
|
||||
.get(&self.main_window_id())
|
||||
.map_or("", |s| s.as_str())
|
||||
.main_window_id()
|
||||
.and_then(|id| self.core().title.get(&id).map(std::string::String::as_str))
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||
fn set_window_title(&mut self, title: String, id: window::Id) -> Command<Self::Message> {
|
||||
#[cfg(feature = "multi-window")]
|
||||
fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message> {
|
||||
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")))]
|
||||
fn set_window_title(&mut self, title: String) -> Command<Self::Message> {
|
||||
let id = self.main_window_id();
|
||||
#[cfg(not(feature = "multi-window"))]
|
||||
fn set_window_title(&mut self, title: String) -> Task<Self::Message> {
|
||||
let Some(id) = self.core().main_window_id() else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
self.core_mut().title.insert(id, title.clone());
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -659,7 +693,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
let is_condensed = core.is_condensed();
|
||||
let focused = core
|
||||
.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 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() {
|
||||
// Manual spacing must be used due to state workarounds below
|
||||
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);
|
||||
|
|
@ -727,7 +761,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
);
|
||||
} else {
|
||||
//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])
|
||||
.width(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")))
|
||||
.into()
|
||||
} else {
|
||||
|
|
@ -770,7 +804,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
} else {
|
||||
Message::Cosmic(cosmic::Message::ToggleNavBar)
|
||||
})
|
||||
.style(crate::theme::Button::HeaderBar);
|
||||
.class(crate::theme::Button::HeaderBar);
|
||||
|
||||
header = header.start(toggle);
|
||||
}
|
||||
|
|
@ -825,11 +859,9 @@ impl<App: Application> ApplicationExt for App {
|
|||
#[cfg(feature = "single-instance")]
|
||||
fn single_instance_subscription<App: ApplicationExt>() -> Subscription<Message<App::Message>> {
|
||||
use iced_futures::futures::StreamExt;
|
||||
|
||||
iced::subscription::channel(
|
||||
iced_futures::Subscription::run_with_id(
|
||||
TypeId::of::<DbusActivation>(),
|
||||
10,
|
||||
move |mut output| async move {
|
||||
iced::stream::channel(10, move |mut output| async move {
|
||||
let mut single_instance: DbusActivation = DbusActivation::new();
|
||||
let mut rx = single_instance.rx();
|
||||
if let Ok(builder) = zbus::ConnectionBuilder::session() {
|
||||
|
|
@ -888,7 +920,7 @@ fn single_instance_subscription<App: ApplicationExt>() -> Subscription<Message<A
|
|||
loop {
|
||||
iced::futures::pending!();
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
238
src/app/multi_window.rs
Normal file
238
src/app/multi_window.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ pub struct Settings {
|
|||
pub(crate) autosize: bool,
|
||||
|
||||
/// Set the application to not create a main window
|
||||
#[cfg(feature = "wayland")]
|
||||
pub(crate) no_main_window: bool,
|
||||
|
||||
/// Whether the window should have a border, a title bar, etc. or not.
|
||||
|
|
@ -77,7 +76,6 @@ impl Default for Settings {
|
|||
antialiasing: true,
|
||||
#[cfg(feature = "wayland")]
|
||||
autosize: false,
|
||||
#[cfg(feature = "wayland")]
|
||||
no_main_window: false,
|
||||
client_decorations: true,
|
||||
debug: false,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
pub mod token;
|
||||
|
||||
use crate::{
|
||||
app::Core,
|
||||
app::{self, iced_settings, Core},
|
||||
cctk::sctk,
|
||||
iced::{
|
||||
self,
|
||||
|
|
@ -10,9 +10,13 @@ use crate::{
|
|||
widget::Container,
|
||||
window, Color, Length, Limits, Rectangle,
|
||||
},
|
||||
iced_style, iced_widget,
|
||||
iced_widget,
|
||||
theme::{self, system_dark, system_light, Button, THEME},
|
||||
widget::{self, layer_container},
|
||||
widget::{
|
||||
self,
|
||||
autosize::{autosize, Autosize},
|
||||
layer_container,
|
||||
},
|
||||
Application, Element, Renderer,
|
||||
};
|
||||
use cctk::sctk::shell::xdg::window::WindowConfigure;
|
||||
|
|
@ -21,14 +25,16 @@ use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
|
|||
use cosmic_theme::Theme;
|
||||
use iced::Pixels;
|
||||
use iced_core::{Padding, Shadow};
|
||||
use iced_style::container::Appearance;
|
||||
use iced_widget::runtime::command::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
use iced_widget::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
|
||||
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;
|
||||
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)]
|
||||
pub struct Context {
|
||||
|
|
@ -37,9 +43,9 @@ pub struct Context {
|
|||
pub background: CosmicPanelBackground,
|
||||
pub output_name: String,
|
||||
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.
|
||||
pub configure: Option<WindowConfigure>,
|
||||
pub suggested_bounds: Option<(iced::Size)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -94,7 +100,7 @@ impl Default for Context {
|
|||
.unwrap_or(CosmicPanelBackground::ThemeDefault),
|
||||
output_name: std::env::var("COSMIC_PANEL_OUTPUT").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 applet_padding = self.suggested_padding(true);
|
||||
let configured_width = self
|
||||
.configure
|
||||
.suggested_bounds
|
||||
.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(|| {
|
||||
NonZeroU32::new(suggested.0 as u32 + applet_padding as u32 * 2).unwrap()
|
||||
});
|
||||
|
||||
let configured_height = self
|
||||
.configure
|
||||
.suggested_bounds
|
||||
.as_ref()
|
||||
.and_then(|c| c.new_size.1.map(|h| h))
|
||||
.and_then(|c| NonZeroU32::new(c.height as u32))
|
||||
.unwrap_or_else(|| {
|
||||
NonZeroU32::new(suggested.1 as u32 + applet_padding as u32 * 2).unwrap()
|
||||
});
|
||||
info!("{configured_height:?}");
|
||||
(configured_width, configured_height)
|
||||
}
|
||||
|
||||
|
|
@ -149,18 +157,15 @@ impl Context {
|
|||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn window_settings(&self) -> crate::app::Settings {
|
||||
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 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()
|
||||
.size(iced_core::Size::new(
|
||||
width + applet_padding as f32 * 2.,
|
||||
height + applet_padding as f32 * 2.,
|
||||
))
|
||||
.size(iced_core::Size::new(width, height))
|
||||
.size_limits(
|
||||
Limits::NONE
|
||||
.min_height(height as f32 + applet_padding as f32 * 2.0)
|
||||
.min_width(width as f32 + applet_padding as f32 * 2.0),
|
||||
.min_height(height as f32)
|
||||
.min_width(width as f32),
|
||||
)
|
||||
.resizable(None)
|
||||
.default_text_size(14.0)
|
||||
|
|
@ -169,6 +174,7 @@ impl Context {
|
|||
if let Some(theme) = self.theme() {
|
||||
settings = settings.theme(theme);
|
||||
}
|
||||
settings.exit_on_close = true;
|
||||
settings
|
||||
}
|
||||
|
||||
|
|
@ -182,26 +188,18 @@ impl Context {
|
|||
&self,
|
||||
icon: widget::icon::Handle,
|
||||
) -> 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 (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();
|
||||
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;
|
||||
|
||||
crate::widget::button::custom(
|
||||
layer_container(
|
||||
widget::icon(icon)
|
||||
.style(if symbolic {
|
||||
theme::Svg::Custom(Rc::new(|theme| crate::iced_style::svg::Appearance {
|
||||
.class(if symbolic {
|
||||
theme::Svg::Custom(Rc::new(|theme| crate::iced_widget::svg::Style {
|
||||
color: Some(theme.cosmic().background.on.into()),
|
||||
}))
|
||||
} else {
|
||||
|
|
@ -215,9 +213,9 @@ impl Context {
|
|||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.width(Length::Fixed(configured_width.get() as f32))
|
||||
.height(Length::Fixed(configured_height.get() as f32))
|
||||
.style(Button::AppletIcon)
|
||||
.width(Length::Fixed((suggested.0 + 2 * applet_padding) as f32))
|
||||
.height(Length::Fixed((suggested.1 + 2 * applet_padding) as f32))
|
||||
.class(Button::AppletIcon)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -225,10 +223,11 @@ impl Context {
|
|||
&self,
|
||||
icon_name: &'a str,
|
||||
) -> crate::widget::Button<'a, Message> {
|
||||
let suggested_size = self.suggested_size(true);
|
||||
self.icon_button_from_handle(
|
||||
widget::icon::from_name(icon_name)
|
||||
.symbolic(true)
|
||||
.size(self.suggested_size(true).0)
|
||||
.size(suggested_size.0)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
|
@ -237,7 +236,7 @@ impl Context {
|
|||
pub fn popup_container<'a, Message: 'static>(
|
||||
&self,
|
||||
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 {
|
||||
PanelAnchor::Left => (Vertical::Center, Horizontal::Left),
|
||||
PanelAnchor::Right => (Vertical::Center, Horizontal::Right),
|
||||
|
|
@ -245,12 +244,12 @@ impl Context {
|
|||
PanelAnchor::Bottom => (Vertical::Bottom, Horizontal::Center),
|
||||
};
|
||||
|
||||
Container::<Message, _, Renderer>::new(
|
||||
Container::<Message, _, Renderer>::new(content).style(theme::Container::custom(
|
||||
|theme| {
|
||||
autosize(
|
||||
Container::<Message, _, Renderer>::new(
|
||||
Container::<Message, _, Renderer>::new(content).style(|theme| {
|
||||
let cosmic = theme.cosmic();
|
||||
let corners = cosmic.corner_radii.clone();
|
||||
Appearance {
|
||||
iced_widget::container::Style {
|
||||
text_color: Some(cosmic.background.on.into()),
|
||||
background: Some(Color::from(cosmic.background.base).into()),
|
||||
border: iced::Border {
|
||||
|
|
@ -261,13 +260,21 @@ impl Context {
|
|||
shadow: Shadow::default(),
|
||||
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]
|
||||
|
|
@ -282,7 +289,7 @@ impl Context {
|
|||
) -> SctkPopupSettings {
|
||||
let (width, height) = self.suggested_size(true);
|
||||
let applet_padding = self.suggested_padding(true);
|
||||
let pixel_offset = 8;
|
||||
let pixel_offset = 4;
|
||||
let (offset, anchor, gravity) = match self.anchor {
|
||||
PanelAnchor::Left => ((pixel_offset, 0), Anchor::Right, Gravity::Right),
|
||||
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]
|
||||
pub fn theme(&self) -> Option<theme::Theme> {
|
||||
match self.background {
|
||||
|
|
@ -348,77 +384,67 @@ impl Context {
|
|||
/// # Errors
|
||||
///
|
||||
/// 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 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);
|
||||
}
|
||||
|
||||
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();
|
||||
core.window.show_window_menu = false;
|
||||
let (iced_settings, (mut core, flags, mut window_settings)) =
|
||||
iced_settings::<App>(settings, flags);
|
||||
core.window.show_headerbar = false;
|
||||
core.window.sharp_corners = true;
|
||||
core.window.show_maximize = false;
|
||||
core.window.show_minimize = false;
|
||||
core.window.use_template = false;
|
||||
|
||||
core.debug = settings.debug;
|
||||
core.set_scale_factor(settings.scale_factor);
|
||||
core.set_window_width(width);
|
||||
core.set_window_height(height);
|
||||
window_settings.decorations = false;
|
||||
window_settings.exit_on_close_request = true;
|
||||
window_settings.resizable = false;
|
||||
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));
|
||||
|
||||
iced.antialiasing = settings.antialiasing;
|
||||
iced.default_font = settings.default_font;
|
||||
iced.default_text_size = settings.default_text_size.into();
|
||||
iced.id = Some(App::APP_ID.to_owned());
|
||||
|
||||
{
|
||||
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()
|
||||
});
|
||||
let mut app = super::app::multi_window::multi_window(
|
||||
cosmic::Cosmic::title,
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
);
|
||||
if core.main_window.get().is_none() {
|
||||
app = app.window(window_settings.clone());
|
||||
_ = core.main_window.set(iced_core::window::Id::RESERVED);
|
||||
}
|
||||
|
||||
<cosmic::Cosmic<App> as iced::Application>::run(iced)
|
||||
app.subscription(cosmic::Cosmic::subscription)
|
||||
.style(cosmic::Cosmic::style)
|
||||
.theme(cosmic::Cosmic::theme)
|
||||
.settings(iced_settings)
|
||||
.run_with(move || cosmic::Cosmic::<App>::init((core, flags, window_settings)))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn style() -> <crate::Theme as iced_style::application::StyleSheet>::Style {
|
||||
<crate::Theme as iced_style::application::StyleSheet>::Style::Custom(Box::new(|theme| {
|
||||
iced_style::application::Appearance {
|
||||
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||
text_color: theme.cosmic().on_bg_color().into(),
|
||||
icon_color: theme.cosmic().on_bg_color().into(),
|
||||
}
|
||||
}))
|
||||
pub fn style() -> iced_runtime::Appearance {
|
||||
let theme = crate::theme::THEME.lock().unwrap();
|
||||
iced_runtime::Appearance {
|
||||
background_color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
|
||||
text_color: theme.cosmic().on_bg_color().into(),
|
||||
icon_color: theme.cosmic().on_bg_color().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn menu_button<'a, Message>(
|
||||
content: impl Into<Element<'a, Message>>,
|
||||
) -> crate::widget::Button<'a, Message> {
|
||||
crate::widget::button::custom(content)
|
||||
.style(Button::AppletMenu)
|
||||
.class(Button::AppletMenu)
|
||||
.padding(menu_control_padding())
|
||||
.width(Length::Fill)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use crate::iced;
|
||||
use crate::iced::subscription;
|
||||
use crate::iced_futures::futures;
|
||||
use cctk::sctk::reexports::calloop;
|
||||
use futures::{
|
||||
channel::mpsc::{unbounded, UnboundedReceiver},
|
||||
SinkExt, StreamExt,
|
||||
};
|
||||
use iced::Subscription;
|
||||
use iced_futures::stream;
|
||||
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
|
||||
|
||||
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>(
|
||||
id: I,
|
||||
) -> iced::Subscription<TokenUpdate> {
|
||||
subscription::channel(id, 50, move |mut output| async move {
|
||||
let mut state = State::Ready;
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
stream::channel(50, move |mut output| async move {
|
||||
let mut state = State::Ready;
|
||||
|
||||
loop {
|
||||
state = start_listening(state, &mut output).await;
|
||||
}
|
||||
})
|
||||
loop {
|
||||
state = start_listening(state, &mut output).await;
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub enum State {
|
||||
|
|
|
|||
|
|
@ -4,114 +4,55 @@
|
|||
//! Create asynchronous actions to be performed in the background.
|
||||
|
||||
use iced::window;
|
||||
use iced::Command;
|
||||
use iced::Task;
|
||||
use iced_core::window::Mode;
|
||||
#[cfg(feature = "wayland")]
|
||||
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 iced_runtime::{task, Action};
|
||||
use std::future::Future;
|
||||
|
||||
/// Yields a command which contains a batch of commands.
|
||||
pub fn batch<X: 'static + Into<Y>, Y: 'static>(
|
||||
commands: impl IntoIterator<Item = Command<X>>,
|
||||
) -> Command<Y> {
|
||||
Command::batch(commands).map(Into::into)
|
||||
pub fn batch<X: Send + 'static + Into<Y>, Y: Send + 'static>(
|
||||
commands: impl IntoIterator<Item = Task<X>>,
|
||||
) -> Task<Y> {
|
||||
Task::batch(commands).map(Into::into)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
Command::single(Action::Future(Box::pin(async move { future.await.into() })))
|
||||
pub fn future<X: Into<Y>, Y: 'static>(future: impl Future<Output = X> + Send + 'static) -> Task<Y> {
|
||||
Task::future(async move { future.await.into() })
|
||||
}
|
||||
|
||||
/// 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() })
|
||||
}
|
||||
|
||||
/// Initiates a window drag.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn drag<M>(id: Option<window::Id>) -> Command<M> {
|
||||
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))
|
||||
pub fn drag<M>(id: window::Id) -> Task<M> {
|
||||
iced_runtime::window::drag(id)
|
||||
}
|
||||
|
||||
/// Maximizes the window.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn maximize<M>(id: Option<window::Id>, maximized: bool) -> Command<M> {
|
||||
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)
|
||||
pub fn maximize<M>(id: window::Id, maximized: bool) -> Task<M> {
|
||||
iced_runtime::window::maximize(id, maximized)
|
||||
}
|
||||
|
||||
/// Minimizes the window.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn minimize<M>(id: Option<window::Id>) -> Command<M> {
|
||||
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)
|
||||
pub fn minimize<M>(id: window::Id) -> Task<M> {
|
||||
iced_runtime::window::minimize(id, true)
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub fn set_title<M>(id: Option<window::Id>, title: String) -> Command<M> {
|
||||
Command::none()
|
||||
pub fn set_title<M>(id: window::Id, title: String) -> Task<M> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Sets the window mode to windowed.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn set_windowed<M>(id: Option<window::Id>) -> Command<M> {
|
||||
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)
|
||||
pub fn set_windowed<M>(id: window::Id) -> Task<M> {
|
||||
iced_runtime::window::change_mode(id, Mode::Windowed)
|
||||
}
|
||||
|
||||
/// Toggles the windows' maximize state.
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn toggle_maximize<M>(id: Option<window::Id>) -> Command<M> {
|
||||
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),
|
||||
)))
|
||||
pub fn toggle_maximize<M>(id: window::Id) -> Task<M> {
|
||||
iced_runtime::window::toggle_maximize(id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Column<'a
|
|||
where
|
||||
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 {
|
||||
|
|
@ -77,7 +77,7 @@ impl<'a, Message: 'a> CollectionWidget<'a, Message> for crate::widget::Row<'a, M
|
|||
where
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! Select preferred fonts.
|
||||
|
||||
pub use iced::Font;
|
||||
use iced_core::font::{Family, Weight};
|
||||
use iced_core::font::Weight;
|
||||
|
||||
pub fn default() -> Font {
|
||||
Font::from(crate::config::interface_font())
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ pub enum Message {
|
|||
}
|
||||
|
||||
pub fn subscription() -> Subscription<Message> {
|
||||
listen_raw(|event, status| {
|
||||
listen_raw(|event, status, _| {
|
||||
if event::Status::Ignored != status {
|
||||
return None;
|
||||
}
|
||||
|
|
|
|||
19
src/lib.rs
19
src/lib.rs
|
|
@ -4,28 +4,25 @@
|
|||
#![allow(clippy::module_name_repetitions)]
|
||||
#![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.
|
||||
pub mod prelude {
|
||||
pub use crate::ext::*;
|
||||
#[cfg(any(feature = "winit", feature = "wayland"))]
|
||||
#[cfg(feature = "winit")]
|
||||
pub use crate::ApplicationExt;
|
||||
pub use crate::{Also, Apply, Element, Renderer, Theme};
|
||||
}
|
||||
|
||||
pub use apply::{Also, Apply};
|
||||
|
||||
#[cfg(any(feature = "winit", feature = "wayland"))]
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod app;
|
||||
#[cfg(any(feature = "winit", feature = "wayland"))]
|
||||
#[cfg(feature = "winit")]
|
||||
pub use app::{Application, ApplicationExt};
|
||||
|
||||
#[cfg(feature = "applet")]
|
||||
pub mod applet;
|
||||
|
||||
pub use iced::Command;
|
||||
pub use iced::Task;
|
||||
pub mod command;
|
||||
|
||||
pub mod config;
|
||||
|
|
@ -62,12 +59,6 @@ pub use iced_renderer;
|
|||
#[doc(inline)]
|
||||
pub use iced_runtime;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub use iced_sctk;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use iced_style;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use iced_widget;
|
||||
|
||||
|
|
@ -96,7 +87,7 @@ pub mod theme;
|
|||
pub use theme::{style, Theme};
|
||||
|
||||
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;
|
||||
pub type Renderer = iced::Renderer;
|
||||
pub type Element<'a, Message> = iced::Element<'a, Message, crate::Theme, Renderer>;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use cosmic_config::CosmicConfigEntry;
|
|||
use cosmic_theme::Component;
|
||||
use cosmic_theme::LayeredTheme;
|
||||
use iced_futures::Subscription;
|
||||
use iced_runtime::{Appearance, DefaultStyle};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
|
@ -273,3 +274,14 @@ impl LayeredTheme for Theme {
|
|||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ashpd::desktop::settings::{ColorScheme, Contrast};
|
||||
use ashpd::desktop::Color;
|
||||
use iced::futures::{self, select, FutureExt, SinkExt, StreamExt};
|
||||
use iced_futures::subscription;
|
||||
use iced_futures::{stream, subscription};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -12,86 +12,92 @@ pub enum Desktop {
|
|||
}
|
||||
|
||||
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
||||
subscription::channel(std::any::TypeId::of::<Desktop>(), 10, |mut tx| {
|
||||
async move {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
let Ok(settings) = ashpd::desktop::settings::Settings::new().await else {
|
||||
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");
|
||||
}
|
||||
|
||||
iced_futures::Subscription::run_with_id(
|
||||
std::any::TypeId::of::<Desktop>(),
|
||||
stream::channel(10, |mut tx| {
|
||||
async move {
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
if color_scheme_stream.is_none() && contrast_stream.is_none() {
|
||||
break;
|
||||
let Ok(settings) = ashpd::desktop::settings::Settings::new().await else {
|
||||
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 {
|
||||
if let Some(s) = contrast_stream.as_mut() {
|
||||
return s.next().await;
|
||||
}
|
||||
futures::future::pending().await
|
||||
};
|
||||
let mut contrast_stream = settings.receive_contrast_changed().await.ok();
|
||||
if contrast_stream.is_none() {
|
||||
error!("Failed to receive contrast changes");
|
||||
}
|
||||
|
||||
select! {
|
||||
s = next_color_scheme.fuse() => {
|
||||
if let Some(s) = s {
|
||||
_ = tx.send(Desktop::ColorScheme(s)).await;
|
||||
} else {
|
||||
color_scheme_stream = None;
|
||||
loop {
|
||||
if color_scheme_stream.is_none() && contrast_stream.is_none() {
|
||||
break;
|
||||
}
|
||||
let next_color_scheme = async {
|
||||
if let Some(s) = color_scheme_stream.as_mut() {
|
||||
return s.next().await;
|
||||
}
|
||||
},
|
||||
futures::future::pending().await
|
||||
};
|
||||
|
||||
c = next_contrast.fuse() => {
|
||||
if let Some(c) = c {
|
||||
_ = tx.send(Desktop::Contrast(c)).await;
|
||||
} else {
|
||||
contrast_stream = None;
|
||||
let next_contrast = async {
|
||||
if let Some(s) = contrast_stream.as_mut() {
|
||||
return s.next().await;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Reset the attempts counter if we successfully received a change
|
||||
attempts = 0;
|
||||
futures::future::pending().await
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,17 @@ use iced_core::{Background, Color};
|
|||
|
||||
use crate::{
|
||||
theme::TRANSPARENT_COMPONENT,
|
||||
widget::button::{Appearance, StyleSheet},
|
||||
widget::button::{Catalog, Style},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum Button {
|
||||
AppletIcon,
|
||||
Custom {
|
||||
active: Box<dyn Fn(bool, &crate::Theme) -> Appearance>,
|
||||
disabled: Box<dyn Fn(&crate::Theme) -> Appearance>,
|
||||
hovered: Box<dyn Fn(bool, &crate::Theme) -> Appearance>,
|
||||
pressed: Box<dyn Fn(bool, &crate::Theme) -> Appearance>,
|
||||
active: Box<dyn Fn(bool, &crate::Theme) -> Style>,
|
||||
disabled: Box<dyn Fn(&crate::Theme) -> Style>,
|
||||
hovered: Box<dyn Fn(bool, &crate::Theme) -> Style>,
|
||||
pressed: Box<dyn Fn(bool, &crate::Theme) -> Style>,
|
||||
},
|
||||
AppletMenu,
|
||||
Destructive,
|
||||
|
|
@ -44,10 +44,10 @@ pub fn appearance(
|
|||
disabled: bool,
|
||||
style: &Button,
|
||||
color: impl Fn(&Component) -> (Color, Option<Color>, Option<Color>),
|
||||
) -> Appearance {
|
||||
) -> Style {
|
||||
let cosmic = theme.cosmic();
|
||||
let mut corner_radii = &cosmic.corner_radii.radius_xl;
|
||||
let mut appearance = Appearance::new();
|
||||
let mut appearance = Style::new();
|
||||
|
||||
match style {
|
||||
Button::Standard
|
||||
|
|
@ -163,10 +163,10 @@ pub fn appearance(
|
|||
appearance
|
||||
}
|
||||
|
||||
impl StyleSheet for crate::Theme {
|
||||
type Style = Button;
|
||||
impl Catalog for crate::Theme {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return pressed(focused, self);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,8 +10,6 @@ mod dropdown;
|
|||
|
||||
pub mod iced;
|
||||
#[doc(inline)]
|
||||
pub use self::iced::Application;
|
||||
#[doc(inline)]
|
||||
pub use self::iced::Checkbox;
|
||||
#[doc(inline)]
|
||||
pub use self::iced::Container;
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ 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 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>(
|
||||
content: T,
|
||||
|
|
@ -117,22 +118,22 @@ where
|
|||
|
||||
/// Centers the contents in the horizontal axis of the [`Container`].
|
||||
#[must_use]
|
||||
pub fn center_x(mut self) -> Self {
|
||||
self.container = self.container.center_x();
|
||||
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) -> Self {
|
||||
self.container = self.container.center_y();
|
||||
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 StyleSheet>::Style>) -> Self {
|
||||
self.container = self.container.style(style);
|
||||
pub fn class(mut self, style: impl Into<crate::style::Container<'a>>) -> Self {
|
||||
self.container = self.container.class(style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -173,9 +174,7 @@ where
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.container.operate(tree, layout, renderer, operation);
|
||||
}
|
||||
|
|
@ -241,8 +240,9 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> 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
276
src/widget/autosize.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{Builder, Style};
|
||||
use super::{Builder, ButtonClass};
|
||||
use crate::widget::{
|
||||
icon::{self, Handle},
|
||||
tooltip,
|
||||
|
|
@ -48,7 +48,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
style: Style::Icon,
|
||||
class: ButtonClass::Icon,
|
||||
variant: icon,
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
|
||||
pub fn vertical(mut self, vertical: bool) -> Self {
|
||||
self.variant.vertical = vertical;
|
||||
self.style = Style::IconVertical;
|
||||
self.class = ButtonClass::IconVertical;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -157,7 +157,7 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
crate::widget::column::with_children(content)
|
||||
.padding(builder.padding)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.align_x(Alignment::Center)
|
||||
.apply(super::custom)
|
||||
} else {
|
||||
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)
|
||||
.height(builder.height)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.align_y(Alignment::Center)
|
||||
.apply(super::custom)
|
||||
};
|
||||
|
||||
|
|
@ -174,18 +174,22 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press)
|
||||
.selected(builder.variant.selected)
|
||||
.style(builder.style);
|
||||
.class(builder.class);
|
||||
|
||||
if builder.tooltip.is_empty() {
|
||||
button.into()
|
||||
} else {
|
||||
tooltip(button, builder.tooltip, tooltip::Position::Top)
|
||||
.size(builder.font_size)
|
||||
.font(crate::font::Font {
|
||||
weight: builder.font_weight,
|
||||
..crate::font::default()
|
||||
})
|
||||
.into()
|
||||
tooltip(
|
||||
button,
|
||||
crate::widget::text(builder.tooltip)
|
||||
.size(builder.font_size)
|
||||
.font(crate::font::Font {
|
||||
weight: builder.font_weight,
|
||||
..crate::font::default()
|
||||
}),
|
||||
tooltip::Position::Top,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
style: Style::Image,
|
||||
class: crate::theme::style::Button::Image,
|
||||
variant,
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ where
|
|||
.selected(builder.variant.selected)
|
||||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press)
|
||||
.style(builder.style)
|
||||
.class(builder.class)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! Hyperlink button widget
|
||||
|
||||
use super::Builder;
|
||||
use super::Style;
|
||||
use super::ButtonClass;
|
||||
use crate::prelude::*;
|
||||
use crate::widget::icon::{self, Handle};
|
||||
use crate::widget::{button, row, tooltip};
|
||||
|
|
@ -44,7 +44,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
style: Style::Link,
|
||||
class: ButtonClass::Link,
|
||||
variant: link,
|
||||
}
|
||||
}
|
||||
|
|
@ -81,23 +81,27 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
.width(builder.width)
|
||||
.height(builder.height)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.align_y(Alignment::Center)
|
||||
.apply(button::custom)
|
||||
.padding(0)
|
||||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press.take())
|
||||
.style(builder.style);
|
||||
.class(builder.class);
|
||||
|
||||
if builder.tooltip.is_empty() {
|
||||
button.into()
|
||||
} else {
|
||||
tooltip(button, builder.tooltip, tooltip::Position::Top)
|
||||
.size(builder.font_size)
|
||||
.font(crate::font::Font {
|
||||
weight: builder.font_weight,
|
||||
..crate::font::default()
|
||||
})
|
||||
.into()
|
||||
tooltip(
|
||||
button,
|
||||
crate::widget::text(builder.tooltip)
|
||||
.size(builder.font_size)
|
||||
.font(crate::font::Font {
|
||||
weight: builder.font_weight,
|
||||
..crate::font::default()
|
||||
}),
|
||||
tooltip::Position::Top,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
//! Button widgets for COSMIC applications.
|
||||
|
||||
pub use crate::theme::Button as Style;
|
||||
pub use crate::theme::Button as ButtonClass;
|
||||
|
||||
pub mod link;
|
||||
use derive_setters::Setters;
|
||||
|
|
@ -26,7 +26,7 @@ pub use image::Button as ImageButton;
|
|||
|
||||
mod style;
|
||||
#[doc(inline)]
|
||||
pub use style::{Appearance, StyleSheet};
|
||||
pub use style::{Catalog, Style};
|
||||
|
||||
mod text;
|
||||
#[doc(inline)]
|
||||
|
|
@ -105,7 +105,7 @@ pub struct Builder<'a, Message, Variant> {
|
|||
font_weight: Weight,
|
||||
|
||||
/// The preferred style of the button.
|
||||
style: Style,
|
||||
class: ButtonClass,
|
||||
|
||||
#[setters(skip)]
|
||||
variant: Variant,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::theme::THEME;
|
|||
/// The appearance of a button.
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Appearance {
|
||||
pub struct Style {
|
||||
/// The amount of offset to apply to the shadow of the button.
|
||||
pub shadow_offset: Vector,
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ pub struct Appearance {
|
|||
pub text_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl Appearance {
|
||||
impl Style {
|
||||
// TODO: `Radius` is not `const fn` compatible.
|
||||
pub fn new() -> Self {
|
||||
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 {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO update to match other styles
|
||||
/// A set of rules that dictate the style of a button.
|
||||
pub trait StyleSheet {
|
||||
pub trait Catalog {
|
||||
/// The supported style of the [`StyleSheet`].
|
||||
type Style: Default;
|
||||
type Class: Default;
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
fn drop_target(&self, style: &Self::Style) -> Appearance {
|
||||
fn drop_target(&self, style: &Self::Class) -> Style {
|
||||
self.hovered(false, false, style)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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
|
||||
fn selection_background(&self) -> Background;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{Builder, Style};
|
||||
use super::{Builder, ButtonClass, Style};
|
||||
use crate::widget::{icon, row, tooltip};
|
||||
use crate::{ext::CollectionWidget, Element};
|
||||
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> {
|
||||
Button::new(Text::new())
|
||||
.label(label)
|
||||
.style(Style::Destructive)
|
||||
.class(ButtonClass::Destructive)
|
||||
}
|
||||
|
||||
/// A text button with the suggested style
|
||||
pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
||||
Button::new(Text::new())
|
||||
.label(label)
|
||||
.style(Style::Suggested)
|
||||
.class(ButtonClass::Suggested)
|
||||
}
|
||||
|
||||
/// 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
|
||||
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.
|
||||
|
|
@ -66,7 +68,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
line_height: 20,
|
||||
font_size: 14,
|
||||
font_weight: Weight::Normal,
|
||||
style: Style::Standard,
|
||||
class: ButtonClass::Standard,
|
||||
variant: text,
|
||||
}
|
||||
}
|
||||
|
|
@ -125,23 +127,27 @@ impl<'a, Message: Clone + 'static> From<Button<'a, Message>> for Element<'a, Mes
|
|||
.width(builder.width)
|
||||
.height(builder.height)
|
||||
.spacing(builder.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.align_y(Alignment::Center)
|
||||
.apply(super::custom)
|
||||
.padding(0)
|
||||
.id(builder.id)
|
||||
.on_press_maybe(builder.on_press.take())
|
||||
.style(builder.style);
|
||||
.class(builder.class);
|
||||
|
||||
if builder.tooltip.is_empty() {
|
||||
button.into()
|
||||
} else {
|
||||
tooltip(button, builder.tooltip, tooltip::Position::Top)
|
||||
.size(builder.font_size)
|
||||
.font(crate::font::Font {
|
||||
weight: builder.font_weight,
|
||||
..crate::font::default()
|
||||
})
|
||||
.into()
|
||||
tooltip(
|
||||
button,
|
||||
crate::widget::text(builder.tooltip)
|
||||
.size(builder.font_size)
|
||||
.font(crate::font::Font {
|
||||
weight: builder.font_weight,
|
||||
..crate::font::default()
|
||||
}),
|
||||
tooltip::Position::Top,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! A [`Button`] has some local [`State`].
|
||||
|
||||
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::renderer::{self, Quad, Renderer};
|
||||
|
|
@ -20,11 +20,11 @@ use iced_core::{overlay, Shadow};
|
|||
use iced_core::{
|
||||
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;
|
||||
|
||||
pub use super::style::{Appearance, StyleSheet};
|
||||
pub use super::style::{Catalog, Style};
|
||||
|
||||
/// Internally defines different button widget variants.
|
||||
enum Variant<Message> {
|
||||
|
|
@ -173,7 +173,7 @@ impl<'a, Message> Button<'a, Message> {
|
|||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
|
|
@ -470,10 +470,11 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
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,
|
||||
crate::widget::common::object_select().clone(),
|
||||
Some(icon_color),
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
|
|
@ -498,11 +499,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
},
|
||||
selection_background,
|
||||
);
|
||||
|
||||
iced_core::svg::Renderer::draw(
|
||||
let svg_handle = svg::Svg::new(close_icon.clone()).color(icon_color);
|
||||
iced_core::svg::Renderer::draw_svg(
|
||||
renderer,
|
||||
close_icon.clone(),
|
||||
Some(icon_color),
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
|
|
@ -533,11 +533,16 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
mut translation: Vector,
|
||||
) -> 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(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -748,11 +753,11 @@ pub fn update<'a, Message: Clone>(
|
|||
pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
||||
renderer: &mut Renderer,
|
||||
bounds: Rectangle,
|
||||
styling: &super::style::Appearance,
|
||||
draw_contents: impl FnOnce(&mut Renderer, &Appearance),
|
||||
styling: &super::style::Style,
|
||||
draw_contents: impl FnOnce(&mut Renderer, &Style),
|
||||
is_image: bool,
|
||||
) where
|
||||
Theme: super::style::StyleSheet,
|
||||
Theme: super::style::Catalog,
|
||||
{
|
||||
let doubled_border_width = styling.border_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`].
|
||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::focusable::focus(id))
|
||||
/// Produces a [`Task`] that focuses the [`Button`] with the given [`Id`].
|
||||
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
|
||||
task::effect(Action::Widget(Box::new(operation::focusable::focus(id))))
|
||||
}
|
||||
|
||||
impl operation::Focusable for State {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ where
|
|||
text(first_day_of_week.to_string())
|
||||
.size(12)
|
||||
.width(Length::Fixed(36.0))
|
||||
.horizontal_alignment(Horizontal::Center),
|
||||
.align_x(Horizontal::Center),
|
||||
);
|
||||
|
||||
first_day_of_week = first_day_of_week.succ();
|
||||
|
|
@ -138,17 +138,17 @@ fn date_button<Message>(
|
|||
on_select: &dyn Fn(NaiveDate) -> Message,
|
||||
) -> crate::widget::Button<'static, Message> {
|
||||
let style = if is_day {
|
||||
button::Style::Suggested
|
||||
button::ButtonClass::Suggested
|
||||
} else {
|
||||
button::Style::Text
|
||||
button::ButtonClass::Text
|
||||
};
|
||||
|
||||
let button = button::custom(
|
||||
text(format!("{}", date.day()))
|
||||
.horizontal_alignment(Horizontal::Center)
|
||||
.vertical_alignment(Vertical::Center),
|
||||
.align_x(Horizontal::Center)
|
||||
.align_y(Vertical::Center),
|
||||
)
|
||||
.style(style)
|
||||
.class(style)
|
||||
.height(Length::Fixed(36.0))
|
||||
.width(Length::Fixed(36.0));
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ use iced_core::{Background, Color};
|
|||
|
||||
/// Appearance of the cards.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Appearance {
|
||||
pub struct Style {
|
||||
pub card_1: Background,
|
||||
pub card_2: Background,
|
||||
}
|
||||
|
||||
impl Default for Appearance {
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
card_1: Background::Color(Color::WHITE),
|
||||
|
|
@ -20,7 +20,7 @@ impl Default for Appearance {
|
|||
}
|
||||
|
||||
/// Defines the [`Appearance`] of a cards.
|
||||
pub trait StyleSheet {
|
||||
pub trait Catalog {
|
||||
/// The default [`Appearance`] of the cards.
|
||||
fn default(&self) -> Appearance;
|
||||
fn default(&self) -> Style;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use crate::theme::iced::Slider;
|
||||
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 derive_setters::Setters;
|
||||
use iced::Command;
|
||||
use iced::Task;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::gradient::{ColorStop, Linear};
|
||||
use iced_core::renderer::Quad;
|
||||
|
|
@ -23,12 +23,11 @@ use iced_core::{
|
|||
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 lazy_static::lazy_static;
|
||||
use palette::{FromColor, RgbHue};
|
||||
|
||||
use super::button::StyleSheet;
|
||||
use super::divider::horizontal;
|
||||
use super::icon::{self, from_name};
|
||||
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 {
|
||||
ColorPickerUpdate::ActiveColor(c) => {
|
||||
self.must_clear_cache.store(true, Ordering::SeqCst);
|
||||
|
|
@ -206,7 +205,7 @@ impl ColorPickerModel {
|
|||
self.copied_at = None;
|
||||
}
|
||||
};
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -298,7 +297,7 @@ where
|
|||
.width(self.width),
|
||||
// canvas with gradient for the current color
|
||||
// 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)
|
||||
.height(self.height),
|
||||
slider(
|
||||
|
|
@ -310,16 +309,21 @@ where
|
|||
on_update(ColorPickerUpdate::ActiveColor(new))
|
||||
}
|
||||
)
|
||||
.style(Slider::Custom {
|
||||
.class(Slider::Custom {
|
||||
active: Rc::new(|t| {
|
||||
let cosmic = t.cosmic();
|
||||
let mut a = slider::StyleSheet::active(t, &Slider::default());
|
||||
a.rail.colors = RailBackground::Gradient {
|
||||
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
|
||||
auto_angle: true,
|
||||
};
|
||||
let mut a =
|
||||
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
|
||||
// a.rail.colors = RailBackground::Gradient {
|
||||
// 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.handle.color = Color::TRANSPARENT;
|
||||
a.handle.background = Color::TRANSPARENT.into();
|
||||
a.handle.shape = HandleShape::Circle { radius: 8.0 };
|
||||
a.handle.border_color = cosmic.palette.neutral_10.into();
|
||||
a.handle.border_width = 4.0;
|
||||
|
|
@ -327,13 +331,15 @@ where
|
|||
}),
|
||||
hovered: Rc::new(|t| {
|
||||
let cosmic = t.cosmic();
|
||||
let mut a = slider::StyleSheet::active(t, &Slider::default());
|
||||
a.rail.colors = RailBackground::Gradient {
|
||||
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
|
||||
auto_angle: true,
|
||||
};
|
||||
let mut a =
|
||||
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
|
||||
// a.rail.colors = RailBackground::Gradient {
|
||||
// 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.handle.color = Color::TRANSPARENT;
|
||||
a.handle.background = Color::TRANSPARENT.into();
|
||||
a.handle.shape = HandleShape::Circle { radius: 8.0 };
|
||||
a.handle.border_color = cosmic.palette.neutral_10.into();
|
||||
a.handle.border_width = 4.0;
|
||||
|
|
@ -341,13 +347,22 @@ where
|
|||
}),
|
||||
dragging: Rc::new(|t| {
|
||||
let cosmic = t.cosmic();
|
||||
let mut a = slider::StyleSheet::active(t, &Slider::default());
|
||||
a.rail.colors = RailBackground::Gradient {
|
||||
gradient: Linear::new(Radians(0.0)).add_stops(HSV_RAINBOW.clone()),
|
||||
auto_angle: true,
|
||||
};
|
||||
let mut a =
|
||||
slider::Catalog::style(t, &Slider::default(), slider::Status::Active);
|
||||
// a.rail.backgrounds = (
|
||||
// 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.handle.color = Color::TRANSPARENT;
|
||||
a.handle.background = Color::TRANSPARENT.into();
|
||||
a.handle.shape = HandleShape::Circle { radius: 8.0 };
|
||||
a.handle.border_color = cosmic.palette.neutral_10.into();
|
||||
a.handle.border_width = 4.0;
|
||||
|
|
@ -373,7 +388,7 @@ where
|
|||
from_name("edit-copy-symbolic").size(spacing.space_s).into(),
|
||||
))
|
||||
.on_press(on_update(ColorPickerUpdate::Copied(Instant::now())))
|
||||
.style(Button::Text);
|
||||
.class(Button::Text);
|
||||
|
||||
match self.copied_at.take() {
|
||||
Some(t) if Instant::now().duration_since(t) > Duration::from_secs(2) => {
|
||||
|
|
@ -381,13 +396,13 @@ where
|
|||
}
|
||||
Some(_) => tooltip(
|
||||
button,
|
||||
copied_to_clipboard_label,
|
||||
text(copied_to_clipboard_label),
|
||||
iced_widget::tooltip::Position::Bottom,
|
||||
)
|
||||
.into(),
|
||||
None => tooltip(
|
||||
button,
|
||||
copy_to_clipboard_label,
|
||||
text(copy_to_clipboard_label),
|
||||
iced_widget::tooltip::Position::Bottom,
|
||||
)
|
||||
.into(),
|
||||
|
|
@ -431,7 +446,7 @@ where
|
|||
)
|
||||
.width(self.width)
|
||||
.direction(iced_widget::scrollable::Direction::Horizontal(
|
||||
scrollable::Properties::new().alignment(scrollable::Alignment::End),
|
||||
scrollable::Scrollbar::new().anchor(scrollable::Anchor::End),
|
||||
))
|
||||
}]
|
||||
.spacing(spacing.space_xxs),
|
||||
|
|
@ -445,7 +460,7 @@ where
|
|||
button::custom(
|
||||
text(reset_to_default)
|
||||
.width(self.width)
|
||||
.horizontal_alignment(iced_core::alignment::Horizontal::Center)
|
||||
.align_x(iced_core::alignment::Horizontal::Center)
|
||||
)
|
||||
.width(self.width)
|
||||
.on_press(on_update(ColorPickerUpdate::Reset))
|
||||
|
|
@ -461,18 +476,18 @@ where
|
|||
button::custom(
|
||||
text(cancel)
|
||||
.width(self.width)
|
||||
.horizontal_alignment(iced_core::alignment::Horizontal::Center)
|
||||
.align_x(iced_core::alignment::Horizontal::Center)
|
||||
)
|
||||
.width(self.width)
|
||||
.on_press(on_update(ColorPickerUpdate::Cancel)),
|
||||
button::custom(
|
||||
text(save)
|
||||
.width(self.width)
|
||||
.horizontal_alignment(iced_core::alignment::Horizontal::Center)
|
||||
.align_x(iced_core::alignment::Horizontal::Center)
|
||||
)
|
||||
.width(self.width)
|
||||
.on_press(on_update(ColorPickerUpdate::AppliedColor))
|
||||
.style(Button::Suggested)
|
||||
.class(Button::Suggested)
|
||||
]
|
||||
.spacing(spacing.space_xs)
|
||||
.width(self.width),
|
||||
|
|
@ -589,7 +604,7 @@ where
|
|||
|
||||
let translation = Vector::new(canvas_layout.bounds().x, canvas_layout.bounds().y);
|
||||
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();
|
||||
|
|
@ -657,10 +672,11 @@ where
|
|||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
self.inner
|
||||
.as_widget_mut()
|
||||
.overlay(&mut state.children[0], layout, renderer)
|
||||
.overlay(&mut state.children[0], layout, renderer, translation)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -782,12 +798,12 @@ pub fn color_button<'a, Message: 'static>(
|
|||
let spacing = THEME.lock().unwrap().cosmic().spacing;
|
||||
|
||||
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 {
|
||||
Element::from(column![
|
||||
vertical_space(Length::FillPortion(6)),
|
||||
vertical_space().height(Length::FillPortion(6)),
|
||||
row![
|
||||
horizontal_space(Length::FillPortion(6)),
|
||||
horizontal_space().width(Length::FillPortion(6)),
|
||||
Icon::from(
|
||||
icon::from_name("list-add-symbolic")
|
||||
.prefer_svg(true)
|
||||
|
|
@ -797,17 +813,17 @@ pub fn color_button<'a, Message: 'static>(
|
|||
.width(icon_portion)
|
||||
.height(Length::Fill)
|
||||
.content_fit(iced_core::ContentFit::Contain),
|
||||
horizontal_space(Length::FillPortion(6)),
|
||||
horizontal_space().width(Length::FillPortion(6)),
|
||||
]
|
||||
.height(icon_portion)
|
||||
.width(Length::Fill),
|
||||
vertical_space(Length::FillPortion(6)),
|
||||
vertical_space().height(Length::FillPortion(6)),
|
||||
])
|
||||
})
|
||||
.width(Length::Fixed(f32::from(spacing.space_s)))
|
||||
.height(Length::Fixed(f32::from(spacing.space_s)))
|
||||
.on_press_maybe(on_press)
|
||||
.style(crate::theme::Button::Custom {
|
||||
.class(crate::theme::Button::Custom {
|
||||
active: Box::new(move |focused, theme| {
|
||||
let cosmic = theme.cosmic();
|
||||
|
||||
|
|
@ -817,7 +833,7 @@ pub fn color_button<'a, Message: 'static>(
|
|||
(0.0, Color::TRANSPARENT)
|
||||
};
|
||||
let standard = theme.active(focused, false, &Button::Standard);
|
||||
button::Appearance {
|
||||
button::Style {
|
||||
shadow_offset: Vector::default(),
|
||||
background: color.map(Background::from).or(standard.background),
|
||||
border_radius: cosmic.radius_xs().into(),
|
||||
|
|
@ -834,7 +850,7 @@ pub fn color_button<'a, Message: 'static>(
|
|||
let cosmic = theme.cosmic();
|
||||
|
||||
let standard = theme.disabled(&Button::Standard);
|
||||
button::Appearance {
|
||||
button::Style {
|
||||
shadow_offset: Vector::default(),
|
||||
background: color.map(Background::from).or(standard.background),
|
||||
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);
|
||||
button::Appearance {
|
||||
button::Style {
|
||||
shadow_offset: Vector::default(),
|
||||
background: color.map(Background::from).or(standard.background),
|
||||
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);
|
||||
button::Appearance {
|
||||
button::Style {
|
||||
shadow_offset: Vector::default(),
|
||||
background: color.map(Background::from).or(standard.background),
|
||||
border_radius: cosmic.radius_xs().into(),
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
use crate::Element;
|
||||
|
||||
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::{Clipboard, Shell};
|
||||
use iced::{event, mouse, Event, Point, Rectangle, Size};
|
||||
use iced_core::Renderer;
|
||||
|
||||
pub(super) struct Overlay<'a, 'b, Message> {
|
||||
pub(crate) position: Point,
|
||||
pub(super) content: &'b mut Element<'a, Message>,
|
||||
pub(super) tree: &'b mut widget::Tree,
|
||||
pub(super) width: f32,
|
||||
|
|
@ -21,13 +22,8 @@ impl<'a, 'b, Message> overlay::Overlay<Message, crate::Theme, crate::Renderer>
|
|||
where
|
||||
Message: Clone,
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &crate::Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_translation: iced::Vector,
|
||||
) -> layout::Node {
|
||||
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let position = self.position;
|
||||
let limits = layout::Limits::new(Size::ZERO, bounds)
|
||||
.width(self.width)
|
||||
.height(bounds.height - 8.0 - position.y);
|
||||
|
|
@ -98,7 +94,7 @@ where
|
|||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
|
|
@ -122,8 +118,9 @@ where
|
|||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
) -> Option<overlay::Element<'c, Message, crate::Theme, crate::Renderer>> {
|
||||
let translation = iced::Vector::new(self.position.x, self.position.y);
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(self.tree, layout, renderer)
|
||||
.overlay(self.tree, layout, renderer, translation)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,9 @@ use iced_core::event::{self, Event};
|
|||
use iced_core::widget::{Operation, Tree};
|
||||
use iced_core::{
|
||||
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]
|
||||
pub struct ContextDrawer<'a, Message> {
|
||||
id: Option<iced_core::widget::Id>,
|
||||
|
|
@ -48,19 +46,19 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
|
|||
text::heading(header)
|
||||
.width(Length::FillPortion(1))
|
||||
.height(Length::Fill)
|
||||
.horizontal_alignment(alignment::Horizontal::Center)
|
||||
.vertical_alignment(alignment::Vertical::Center),
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.align_y(alignment::Vertical::Center),
|
||||
)
|
||||
.push(
|
||||
button::text("Close")
|
||||
.trailing_icon(icon::from_name("go-next-symbolic"))
|
||||
.on_press(on_close)
|
||||
.style(crate::theme::Button::Link)
|
||||
.class(crate::theme::Button::Link)
|
||||
.apply(container)
|
||||
.width(Length::FillPortion(1))
|
||||
.height(Length::Fill)
|
||||
.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
|
||||
.height(Length::Fixed(80.0))
|
||||
|
|
@ -84,7 +82,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
|
|||
container(
|
||||
LayerContainer::new(pane)
|
||||
.layer(cosmic_theme::Layer::Primary)
|
||||
.style(crate::style::Container::ContextDrawer)
|
||||
.class(crate::style::Container::ContextDrawer)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.max_width(max_width),
|
||||
|
|
@ -159,7 +157,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDraw
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
|
|
@ -232,17 +230,20 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDraw
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
Some(iced_overlay::Element::new(
|
||||
layout.position(),
|
||||
Box::new(Overlay {
|
||||
content: &mut self.drawer,
|
||||
tree: &mut tree.children[1],
|
||||
width: bounds.width,
|
||||
}),
|
||||
))
|
||||
let mut position = layout.position();
|
||||
position.x += translation.x;
|
||||
position.y += translation.y;
|
||||
|
||||
Some(iced_overlay::Element::new(Box::new(Overlay {
|
||||
content: &mut self.drawer,
|
||||
tree: &mut tree.children[1],
|
||||
width: bounds.width,
|
||||
position,
|
||||
})))
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::widget::menu::{
|
|||
};
|
||||
use derive_setters::Setters;
|
||||
use iced::touch::Finger;
|
||||
use iced::Event;
|
||||
use iced::{Event, Vector};
|
||||
use iced_core::widget::{tree, Tree, Widget};
|
||||
use iced_core::{event, mouse, touch, Length, Point, Size};
|
||||
use std::collections::HashSet;
|
||||
|
|
@ -144,9 +144,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &mut Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
|
|
@ -213,6 +211,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &'b mut Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
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],
|
||||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
style: &crate::theme::menu_bar::MenuBarStyle::Default,
|
||||
position: Point::new(translation.x, translation.y),
|
||||
}
|
||||
.overlay(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
content_col = content_col.push(widget::text::title3(dialog.title));
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +92,7 @@ impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Mes
|
|||
if let Some(button) = dialog.tertiary_action {
|
||||
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 {
|
||||
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()])
|
||||
.spacing(space_l),
|
||||
)
|
||||
.style(style::Container::Dialog)
|
||||
.class(style::Container::Dialog)
|
||||
.padding(space_m)
|
||||
.width(Length::Fixed(570.0)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ use std::{
|
|||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
use iced::Vector;
|
||||
|
||||
use crate::{
|
||||
iced::{
|
||||
clipboard::{
|
||||
|
|
@ -280,9 +282,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.container
|
||||
.as_widget()
|
||||
|
|
@ -496,10 +496,11 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree: &'b mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
self.container
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer)
|
||||
.overlay(&mut tree.children[0], layout, renderer, translation)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
|
|||
|
|
@ -1,69 +1,77 @@
|
|||
use std::any::Any;
|
||||
|
||||
use iced_core::window;
|
||||
|
||||
use crate::{
|
||||
iced::{
|
||||
clipboard::dnd::{DndAction, DndEvent, SourceEvent},
|
||||
event, mouse, overlay, Event, Length, Point, Rectangle,
|
||||
event, mouse, overlay, Event, Length, Point, Rectangle, Vector,
|
||||
},
|
||||
iced_core::{
|
||||
self, layout, renderer,
|
||||
widget::{tree, Tree},
|
||||
Clipboard, Shell,
|
||||
},
|
||||
iced_style,
|
||||
widget::{container, Id, Widget},
|
||||
Element,
|
||||
};
|
||||
|
||||
pub fn dnd_source<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
Message: Clone + 'static,
|
||||
D: iced::clipboard::mime::AsMimeTypes + Send + 'static,
|
||||
>(
|
||||
child: impl Into<Element<'a, Message>>,
|
||||
) -> DndSource<'a, Message, AppMessage, D> {
|
||||
) -> DndSource<'a, Message, D> {
|
||||
DndSource::new(child)
|
||||
}
|
||||
|
||||
pub struct DndSource<'a, Message, AppMessage, D> {
|
||||
pub struct DndSource<'a, Message, D> {
|
||||
id: Id,
|
||||
action: DndAction,
|
||||
container: Element<'a, Message>,
|
||||
window: Option<window::Id>,
|
||||
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,
|
||||
_phantom: std::marker::PhantomData<AppMessage>,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
Message: Clone + '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 {
|
||||
Self {
|
||||
id: Id::unique(),
|
||||
window: None,
|
||||
action: DndAction::Copy | DndAction::Move,
|
||||
container: container(child).into(),
|
||||
drag_content: None,
|
||||
drag_icon: None,
|
||||
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 {
|
||||
Self {
|
||||
id,
|
||||
window: None,
|
||||
action: DndAction::Copy | DndAction::Move,
|
||||
container: container(child).into(),
|
||||
drag_content: None,
|
||||
drag_icon: None,
|
||||
drag_threshold: 8.0,
|
||||
_phantom: std::marker::PhantomData,
|
||||
on_start: None,
|
||||
on_cancelled: None,
|
||||
on_finish: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +90,7 @@ impl<
|
|||
#[must_use]
|
||||
pub fn drag_icon(
|
||||
mut self,
|
||||
f: impl Fn() -> (Element<'static, AppMessage>, tree::State) + 'static,
|
||||
f: impl Fn() -> (Element<'static, ()>, tree::State) + 'static,
|
||||
) -> Self {
|
||||
self.drag_icon = Some(Box::new(f));
|
||||
self
|
||||
|
|
@ -98,10 +106,15 @@ impl<
|
|||
let Some(content) = self.drag_content.as_ref().map(|f| f()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
iced_core::clipboard::start_dnd(
|
||||
clipboard,
|
||||
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| {
|
||||
let (icon, state) = f();
|
||||
(
|
||||
|
|
@ -116,14 +129,33 @@ impl<
|
|||
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<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
Message: Clone + '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> {
|
||||
vec![Tree::new(&self.container)]
|
||||
|
|
@ -165,9 +197,7 @@ impl<
|
|||
tree: &mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id));
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
|
|
@ -210,7 +240,6 @@ impl<
|
|||
}
|
||||
|
||||
state.left_pressed_position = Some(position);
|
||||
// dbg!(&state, &self.id);
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
|
@ -229,8 +258,10 @@ impl<
|
|||
return ret;
|
||||
}
|
||||
if let Some(left_pressed_position) = state.left_pressed_position {
|
||||
// dbg!(&state);
|
||||
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);
|
||||
state.is_dragging = true;
|
||||
state.left_pressed_position = None;
|
||||
|
|
@ -249,8 +280,21 @@ impl<
|
|||
}
|
||||
_ => return ret,
|
||||
},
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled | SourceEvent::Finished)) => {
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
|
||||
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;
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
|
@ -308,10 +352,11 @@ impl<
|
|||
tree: &'b mut Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
self.container
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer)
|
||||
.overlay(&mut tree.children[0], layout, renderer, translation)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -319,7 +364,7 @@ impl<
|
|||
state: &Tree,
|
||||
layout: layout::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.container.as_widget().drag_destinations(
|
||||
&state.children[0],
|
||||
|
|
@ -340,12 +385,11 @@ impl<
|
|||
|
||||
impl<
|
||||
'a,
|
||||
Message: 'static,
|
||||
AppMessage: 'static,
|
||||
Message: Clone + '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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
|
|||
position: Point,
|
||||
target_height: f32,
|
||||
) -> 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,
|
||||
target_height: f32,
|
||||
style: (),
|
||||
position: Point,
|
||||
}
|
||||
|
||||
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 {
|
||||
state,
|
||||
options,
|
||||
|
|
@ -160,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
|
||||
container = container
|
||||
.padding(padding)
|
||||
.style(crate::style::Container::Dropdown);
|
||||
.class(crate::style::Container::Dropdown);
|
||||
|
||||
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
|
||||
|
||||
|
|
@ -170,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
width,
|
||||
target_height,
|
||||
style,
|
||||
position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||
for Overlay<'a, Message>
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &crate::Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_translation: iced::Vector,
|
||||
) -> layout::Node {
|
||||
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let position = self.position;
|
||||
let space_below = bounds.height - (position.y + self.target_height);
|
||||
let space_above = position.y;
|
||||
|
||||
|
|
@ -447,10 +448,13 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
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,
|
||||
crate::widget::common::object_select().clone(),
|
||||
Some(appearance.selected_text_color),
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
x: item_x + item_width - 16.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(
|
||||
renderer,
|
||||
Text {
|
||||
content: option.as_ref(),
|
||||
content: option.as_ref().to_string(),
|
||||
bounds: bounds.size(),
|
||||
size: Pixels(text_size),
|
||||
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,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
bounds.position(),
|
||||
color,
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ where
|
|||
position: Point,
|
||||
target_height: f32,
|
||||
) -> 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,
|
||||
target_height: f32,
|
||||
style: (),
|
||||
position: Point,
|
||||
}
|
||||
|
||||
impl<'a, Message: 'a> Overlay<'a, Message> {
|
||||
pub fn new<S: AsRef<str>, Item: Clone + PartialEq>(
|
||||
menu: Menu<'a, S, Item, Message>,
|
||||
target_height: f32,
|
||||
position: Point,
|
||||
) -> Self {
|
||||
let Menu {
|
||||
state,
|
||||
|
|
@ -163,7 +165,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
|
||||
container = container
|
||||
.padding(padding)
|
||||
.style(crate::style::Container::Dropdown);
|
||||
.class(crate::style::Container::Dropdown);
|
||||
|
||||
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
|
||||
|
||||
|
|
@ -173,6 +175,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
width,
|
||||
target_height,
|
||||
style,
|
||||
position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -180,13 +183,8 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||
for Overlay<'a, Message>
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &crate::Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_translation: iced::Vector,
|
||||
) -> layout::Node {
|
||||
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let position = self.position;
|
||||
let space_below = bounds.height - (position.y + self.target_height);
|
||||
let space_above = position.y;
|
||||
|
||||
|
|
@ -537,10 +535,13 @@ where
|
|||
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,
|
||||
crate::widget::common::object_select().clone(),
|
||||
Some(appearance.selected_text_color),
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
x: item_x + item_width - 16.0 - 8.0,
|
||||
y: bounds.y + (bounds.height / 2.0 - 8.0),
|
||||
|
|
@ -587,7 +588,7 @@ where
|
|||
text::Renderer::fill_text(
|
||||
renderer,
|
||||
Text {
|
||||
content: option.as_ref(),
|
||||
content: option.as_ref().to_string(),
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(text_size),
|
||||
line_height: self.text_line_height,
|
||||
|
|
@ -595,7 +596,7 @@ where
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
bounds.position(),
|
||||
color,
|
||||
|
|
@ -636,7 +637,7 @@ where
|
|||
text::Renderer::fill_text(
|
||||
renderer,
|
||||
Text {
|
||||
content: description.as_ref(),
|
||||
content: description.as_ref().to_string(),
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(text_size),
|
||||
line_height: text::LineHeight::Absolute(Pixels(text_line_height + 4.0)),
|
||||
|
|
@ -644,7 +645,7 @@ where
|
|||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
bounds.position(),
|
||||
appearance.description_color,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub mod menu;
|
|||
pub use menu::Menu;
|
||||
|
||||
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>(
|
||||
model: &'a Model<S, Item>,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ use iced_core::event::{self, Event};
|
|||
use iced_core::text::{self, Paragraph, Text};
|
||||
use iced_core::widget::tree::{self, Tree};
|
||||
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;
|
||||
|
||||
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.
|
||||
#[derive(Setters)]
|
||||
|
|
@ -98,7 +101,7 @@ impl<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static>
|
|||
for (_, item) in &list.options {
|
||||
state
|
||||
.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,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State<Item>>();
|
||||
|
||||
|
|
@ -216,8 +220,8 @@ pub struct State<Item: Clone + PartialEq + 'static> {
|
|||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<Item>,
|
||||
selections: Vec<(Item, crate::Paragraph)>,
|
||||
descriptions: Vec<crate::Paragraph>,
|
||||
selections: Vec<(Item, crate::Plain)>,
|
||||
descriptions: Vec<crate::Plain>,
|
||||
}
|
||||
|
||||
impl<Item: Clone + PartialEq + 'static> State<Item> {
|
||||
|
|
@ -259,7 +263,7 @@ pub fn layout(
|
|||
text_size: f32,
|
||||
text_line_height: text::LineHeight,
|
||||
font: Option<crate::font::Font>,
|
||||
selection: Option<(&str, &mut crate::Paragraph)>,
|
||||
selection: Option<(&str, &mut crate::Plain)>,
|
||||
) -> layout::Node {
|
||||
use std::f32;
|
||||
|
||||
|
|
@ -267,7 +271,7 @@ pub fn layout(
|
|||
|
||||
let max_width = match width {
|
||||
Length::Shrink => {
|
||||
let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 {
|
||||
let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 {
|
||||
paragraph.update(Text {
|
||||
content: label,
|
||||
bounds: Size::new(f32::MAX, f32::MAX),
|
||||
|
|
@ -277,7 +281,7 @@ pub fn layout(
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
});
|
||||
paragraph.min_width().round()
|
||||
};
|
||||
|
|
@ -410,7 +414,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
|
|||
)
|
||||
.width({
|
||||
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 {
|
||||
content: label,
|
||||
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,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
});
|
||||
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 {
|
||||
&mut state.descriptions[desc_count]
|
||||
} else {
|
||||
state.descriptions.push(crate::Paragraph::new());
|
||||
state.descriptions.push(crate::Plain::default());
|
||||
state.descriptions.last_mut().unwrap()
|
||||
};
|
||||
desc_count += 1;
|
||||
|
|
@ -448,7 +452,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a, Item: Clone + PartialEq + 'static
|
|||
None => {
|
||||
state
|
||||
.selections
|
||||
.push((item.clone(), crate::Paragraph::new()));
|
||||
.push((item.clone(), crate::Plain::default()));
|
||||
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 style = if is_mouse_over {
|
||||
theme.hovered(&())
|
||||
theme.style(&(), pick_list::Status::Hovered)
|
||||
} else {
|
||||
theme.active(&())
|
||||
theme.style(&(), pick_list::Status::Active)
|
||||
};
|
||||
|
||||
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() {
|
||||
svg::Renderer::draw(
|
||||
let svg_handle = iced_core::Svg::new(handle).color(style.text_color);
|
||||
svg::Renderer::draw_svg(
|
||||
renderer,
|
||||
handle,
|
||||
Some(style.text_color),
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
x: bounds.x + bounds.width - gap - 16.0,
|
||||
y: bounds.center_y() - 8.0,
|
||||
|
|
@ -541,7 +545,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
|
|||
text::Renderer::fill_text(
|
||||
renderer,
|
||||
Text {
|
||||
content,
|
||||
content: content.to_string(),
|
||||
size: iced::Pixels(text_size),
|
||||
line_height: text_line_height,
|
||||
font,
|
||||
|
|
@ -549,7 +553,7 @@ pub fn draw<'a, S, Item: Clone + PartialEq + 'static>(
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
bounds.position(),
|
||||
style.text_color,
|
||||
|
|
|
|||
|
|
@ -5,16 +5,18 @@
|
|||
use super::menu::{self, Menu};
|
||||
use crate::widget::icon;
|
||||
use derive_setters::Setters;
|
||||
use iced::Radians;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::text::{self, Paragraph, Text};
|
||||
use iced_core::widget::tree::{self, Tree};
|
||||
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::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.
|
||||
#[derive(Setters)]
|
||||
pub struct Dropdown<'a, S: AsRef<str>, Message> {
|
||||
|
|
@ -62,6 +64,29 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
|||
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>
|
||||
|
|
@ -80,7 +105,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
|||
|
||||
state
|
||||
.selections
|
||||
.resize_with(self.selections.len(), crate::Paragraph::new);
|
||||
.resize_with(self.selections.len(), crate::Plain::default);
|
||||
state.hashes.resize(self.selections.len(), 0);
|
||||
|
||||
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,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
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,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
|
|
@ -237,8 +263,8 @@ pub struct State {
|
|||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<usize>,
|
||||
selections: Vec<crate::Paragraph>,
|
||||
hashes: Vec<u64>,
|
||||
selections: Vec<crate::Plain>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -280,7 +306,7 @@ pub fn layout(
|
|||
text_size: f32,
|
||||
text_line_height: text::LineHeight,
|
||||
font: Option<crate::font::Font>,
|
||||
selection: Option<(&str, &mut crate::Paragraph)>,
|
||||
selection: Option<(&str, &mut crate::Plain)>,
|
||||
) -> layout::Node {
|
||||
use std::f32;
|
||||
|
||||
|
|
@ -288,7 +314,7 @@ pub fn layout(
|
|||
|
||||
let max_width = match width {
|
||||
Length::Shrink => {
|
||||
let measure = move |(label, paragraph): (_, &mut crate::Paragraph)| -> f32 {
|
||||
let measure = move |(label, paragraph): (_, &mut crate::Plain)| -> f32 {
|
||||
paragraph.update(Text {
|
||||
content: label,
|
||||
bounds: Size::new(f32::MAX, f32::MAX),
|
||||
|
|
@ -298,7 +324,7 @@ pub fn layout(
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
});
|
||||
paragraph.min_width().round()
|
||||
};
|
||||
|
|
@ -430,14 +456,14 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
|
|||
None,
|
||||
)
|
||||
.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()
|
||||
};
|
||||
|
||||
selections
|
||||
.iter()
|
||||
.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))
|
||||
+ gap
|
||||
+ 16.0
|
||||
|
|
@ -477,9 +503,9 @@ pub fn draw<'a, S>(
|
|||
let is_mouse_over = cursor.is_over(bounds);
|
||||
|
||||
let style = if is_mouse_over {
|
||||
theme.hovered(&())
|
||||
theme.style(&(), pick_list::Status::Hovered)
|
||||
} else {
|
||||
theme.active(&())
|
||||
theme.style(&(), pick_list::Status::Active)
|
||||
};
|
||||
|
||||
iced_core::Renderer::fill_quad(
|
||||
|
|
@ -493,10 +519,11 @@ pub fn draw<'a, S>(
|
|||
);
|
||||
|
||||
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,
|
||||
handle,
|
||||
Some(style.text_color),
|
||||
svg_handle,
|
||||
Rectangle {
|
||||
x: bounds.x + bounds.width - gap - 16.0,
|
||||
y: bounds.center_y() - 8.0,
|
||||
|
|
@ -517,7 +544,7 @@ pub fn draw<'a, S>(
|
|||
text::Renderer::fill_text(
|
||||
renderer,
|
||||
Text {
|
||||
content,
|
||||
content: content.to_string(),
|
||||
size: iced::Pixels(text_size),
|
||||
line_height: text_line_height,
|
||||
font,
|
||||
|
|
@ -525,7 +552,7 @@ pub fn draw<'a, S>(
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
bounds.position(),
|
||||
style.text_color,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ use derive_setters::Setters;
|
|||
use iced_core::event::{self, Event};
|
||||
use iced_core::widget::{Operation, Tree};
|
||||
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.
|
||||
#[derive(Setters)]
|
||||
|
|
@ -132,7 +132,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
self.children
|
||||
|
|
@ -225,8 +225,9 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> 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")]
|
||||
|
|
@ -252,7 +253,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
for ((e, layout), state) in self
|
||||
.children
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ use iced_core::event::{self, Event};
|
|||
use iced_core::widget::{Operation, Tree};
|
||||
use iced_core::{
|
||||
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.
|
||||
#[must_use]
|
||||
|
|
@ -154,7 +153,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
operation.container(None, layout.bounds(), &mut |operation| {
|
||||
self.children
|
||||
|
|
@ -247,8 +246,9 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> 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")]
|
||||
|
|
@ -274,7 +274,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
for ((e, layout), state) in self
|
||||
.children
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::{ext::CollectionWidget, widget, Element};
|
|||
use apply::Apply;
|
||||
use derive_setters::Setters;
|
||||
use iced::Length;
|
||||
use iced_core::{widget::tree, Widget};
|
||||
use iced_core::{widget::tree, Vector, Widget};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -221,9 +221,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
|
|||
state: &mut tree::Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
let child_tree = &mut state.children[0];
|
||||
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,
|
||||
layout: iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let child_tree = &mut state.children[0];
|
||||
let child_layout = layout.children().next().unwrap();
|
||||
self.header_bar_inner
|
||||
.as_widget_mut()
|
||||
.overlay(child_tree, child_layout, renderer)
|
||||
self.header_bar_inner.as_widget_mut().overlay(
|
||||
child_tree,
|
||||
child_layout,
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -250,7 +252,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
|
|||
state: &tree::Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
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)) =
|
||||
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);
|
||||
|
||||
// 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());
|
||||
|
||||
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.
|
||||
.push(
|
||||
widget::row::with_children(start)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.align_x(iced::alignment::Horizontal::Left)
|
||||
.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.
|
||||
.push(if !center.is_empty() {
|
||||
widget::row::with_children(center)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
} else if self.title.is_empty() {
|
||||
widget::horizontal_space(Length::Fill).into()
|
||||
widget::horizontal_space().width(Length::Fill).into()
|
||||
} else {
|
||||
self.title_widget()
|
||||
})
|
||||
.push(
|
||||
widget::row::with_children(end)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.align_x(iced::alignment::Horizontal::Right)
|
||||
.width(Length::Shrink),
|
||||
)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.height(Length::Fixed(height))
|
||||
.padding([0, 8])
|
||||
.spacing(8)
|
||||
.apply(widget::container)
|
||||
.style(crate::theme::Container::HeaderBar {
|
||||
.class(crate::theme::Container::HeaderBar {
|
||||
focused: self.focused,
|
||||
})
|
||||
.center_y()
|
||||
.center_y(Length::Shrink)
|
||||
.apply(widget::mouse_area);
|
||||
|
||||
// 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)
|
||||
.apply(widget::container)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +380,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
.padding(8)
|
||||
};
|
||||
|
||||
icon.style(crate::theme::Button::HeaderBar)
|
||||
icon.class(crate::theme::Button::HeaderBar)
|
||||
.selected(self.focused)
|
||||
.icon_size($size)
|
||||
.on_press($on_press)
|
||||
|
|
@ -405,8 +405,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
)
|
||||
.spacing(8)
|
||||
.apply(widget::container)
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.center_y(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,10 @@ pub fn from_raster_bytes(
|
|||
) -> Handle {
|
||||
Handle {
|
||||
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]>>
|
||||
+ std::convert::AsRef<[u8]>
|
||||
+ std::marker::Send
|
||||
+ std::marker::Sync
|
||||
+ 'static,
|
||||
+ std::marker::Sync,
|
||||
) -> Handle {
|
||||
Handle {
|
||||
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)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub fn icon(handle: Handle) -> Icon {
|
|||
handle,
|
||||
height: None,
|
||||
size: 16,
|
||||
style: crate::theme::Svg::default(),
|
||||
class: crate::theme::Svg::default(),
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ pub fn from_name(name: impl Into<Arc<str>>) -> Named {
|
|||
pub struct Icon {
|
||||
#[setters(skip)]
|
||||
handle: Handle,
|
||||
style: crate::theme::Svg,
|
||||
class: crate::theme::Svg,
|
||||
pub(super) size: u16,
|
||||
content_fit: ContentFit,
|
||||
#[setters(strip_option)]
|
||||
|
|
@ -86,7 +86,7 @@ impl Icon {
|
|||
|
||||
let from_svg = |handle| {
|
||||
Svg::<crate::Theme>::new(handle)
|
||||
.style(self.style.clone())
|
||||
.class(self.class.clone())
|
||||
.width(
|
||||
self.width
|
||||
.unwrap_or_else(|| Length::Fixed(f32::from(self.size))),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ 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, Widget};
|
||||
pub use iced_style::container::{Appearance, StyleSheet};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
pub use iced_widget::container::{Catalog, Style};
|
||||
|
||||
pub fn id_container<'a, Message: 'static, Theme, E>(
|
||||
content: E,
|
||||
|
|
@ -13,8 +13,8 @@ pub fn id_container<'a, Message: 'static, Theme, E>(
|
|||
) -> IdContainer<'a, Message, Theme, crate::Renderer>
|
||||
where
|
||||
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
||||
Theme: iced_style::container::StyleSheet,
|
||||
<Theme as iced_widget::container::StyleSheet>::Style: From<crate::theme::Container>,
|
||||
Theme: iced_widget::container::Catalog,
|
||||
<Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
|
||||
{
|
||||
IdContainer::new(content, id)
|
||||
}
|
||||
|
|
@ -83,9 +83,7 @@ where
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
|
|
@ -165,11 +163,13 @@ where
|
|||
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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ where
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
self.content.as_widget().drag_destinations(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::Theme;
|
||||
use cosmic_theme::LayeredTheme;
|
||||
use iced::widget::Container;
|
||||
use iced_core::alignment;
|
||||
|
|
@ -7,16 +8,14 @@ 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};
|
||||
pub use iced_style::container::{Appearance, StyleSheet};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget};
|
||||
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,
|
||||
) -> LayerContainer<'a, Message, Theme, crate::Renderer>
|
||||
) -> LayerContainer<'a, Message, crate::Renderer>
|
||||
where
|
||||
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)
|
||||
}
|
||||
|
|
@ -25,20 +24,18 @@ where
|
|||
///
|
||||
/// It is normally used for alignment purposes.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct LayerContainer<'a, Message, Theme, Renderer>
|
||||
pub struct LayerContainer<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer,
|
||||
Theme: iced_style::container::StyleSheet + LayeredTheme,
|
||||
{
|
||||
layer: Option<cosmic_theme::Layer>,
|
||||
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
|
||||
Renderer: iced_core::Renderer,
|
||||
Theme: iced_style::container::StyleSheet + LayeredTheme,
|
||||
<Theme as iced_style::container::StyleSheet>::Style: From<crate::theme::Container>,
|
||||
// iced_widget::container::Style: From<crate::theme::Container>,
|
||||
{
|
||||
/// Creates an empty [`Container`].
|
||||
pub(crate) fn new<T>(content: T) -> Self
|
||||
|
|
@ -55,7 +52,7 @@ where
|
|||
#[must_use]
|
||||
pub fn layer(mut self, layer: cosmic_theme::Layer) -> Self {
|
||||
self.layer = Some(layer);
|
||||
self.style(match layer {
|
||||
self.class(match layer {
|
||||
cosmic_theme::Layer::Background => crate::theme::Container::Background,
|
||||
cosmic_theme::Layer::Primary => crate::theme::Container::Primary,
|
||||
cosmic_theme::Layer::Secondary => crate::theme::Container::Secondary,
|
||||
|
|
@ -113,31 +110,30 @@ where
|
|||
|
||||
/// Centers the contents in the horizontal axis of the [`LayerContainer`].
|
||||
#[must_use]
|
||||
pub fn center_x(mut self) -> Self {
|
||||
self.container = self.container.center_x();
|
||||
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 [`LayerContainer`].
|
||||
#[must_use]
|
||||
pub fn center_y(mut self) -> Self {
|
||||
self.container = self.container.center_y();
|
||||
pub fn center_y(mut self, height: Length) -> Self {
|
||||
self.container = self.container.center_y(height);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`LayerContainer`].
|
||||
#[must_use]
|
||||
pub fn style(mut self, style: impl Into<<Theme as StyleSheet>::Style>) -> Self {
|
||||
self.container = self.container.style(style);
|
||||
pub fn class(mut self, style: impl Into<crate::style::iced::Container<'a>>) -> Self {
|
||||
self.container = self.container.class(style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for LayerContainer<'a, Message, Theme, Renderer>
|
||||
impl<'a, Message, Renderer> Widget<Message, Theme, Renderer>
|
||||
for LayerContainer<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer,
|
||||
Theme: iced_style::container::StyleSheet + LayeredTheme + Clone,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.container.children()
|
||||
|
|
@ -173,9 +169,7 @@ where
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.container.operate(tree, layout, renderer, operation);
|
||||
}
|
||||
|
|
@ -232,6 +226,7 @@ where
|
|||
} else {
|
||||
theme.clone()
|
||||
};
|
||||
|
||||
self.container.draw(
|
||||
tree,
|
||||
renderer,
|
||||
|
|
@ -248,8 +243,9 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.container.overlay(tree, layout, renderer)
|
||||
self.container.overlay(tree, layout, renderer, translation)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -257,7 +253,7 @@ where
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.container
|
||||
.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>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + iced_core::Renderer,
|
||||
Theme: iced_style::container::StyleSheet + LayeredTheme + 'a + Clone,
|
||||
{
|
||||
fn from(
|
||||
column: LayerContainer<'a, Message, Theme, Renderer>,
|
||||
column: LayerContainer<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
Element::new(column)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use iced_core::Padding;
|
||||
use iced_style::container::StyleSheet;
|
||||
use iced_widget::container::Catalog;
|
||||
|
||||
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> {
|
||||
spacing: u16,
|
||||
padding: Padding,
|
||||
style: <crate::Theme as StyleSheet>::Style,
|
||||
style: crate::theme::Container<'a>,
|
||||
children: Vec<Element<'a, Message>>,
|
||||
}
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
|
|||
Self {
|
||||
spacing: theme::THEME.lock().unwrap().cosmic().spacing.space_xxs,
|
||||
padding: Padding::from(0),
|
||||
style: <crate::Theme as StyleSheet>::Style::List,
|
||||
style: crate::theme::Container::List,
|
||||
children: Vec::with_capacity(4),
|
||||
}
|
||||
}
|
||||
|
|
@ -41,9 +41,11 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|||
}
|
||||
|
||||
// Ensure a minimum height of 32.
|
||||
let container = crate::widget::container(item)
|
||||
.min_height(32)
|
||||
.align_y(iced::alignment::Vertical::Center);
|
||||
let container = iced::widget::row![
|
||||
crate::widget::container(item).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
|
||||
|
|
@ -55,7 +57,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
|
@ -72,7 +74,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|||
.padding(self.padding)
|
||||
.apply(super::container)
|
||||
.padding([self.spacing, 8])
|
||||
.style(self.style)
|
||||
.class(self.style)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ pub fn container<'a, Message>(
|
|||
) -> Container<'a, Message, crate::Theme, crate::Renderer> {
|
||||
super::container(content)
|
||||
.padding([16, 6])
|
||||
.style(crate::theme::Container::List)
|
||||
.class(crate::theme::Container::List)
|
||||
.width(iced::Length::Fill)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use super::{
|
|||
};
|
||||
use crate::style::menu_bar::StyleSheet;
|
||||
|
||||
use iced::{Point, Vector};
|
||||
use iced_core::Border;
|
||||
use iced_widget::core::{
|
||||
event,
|
||||
|
|
@ -434,6 +435,7 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
let state = tree.state.downcast_ref::<MenuBarState>();
|
||||
if !state.open {
|
||||
|
|
@ -455,6 +457,7 @@ where
|
|||
root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(),
|
||||
path_highlight: self.path_highlight,
|
||||
style: &self.style,
|
||||
position: Point::new(translation.x, translation.y),
|
||||
}
|
||||
.overlay(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ impl MenuBounds {
|
|||
let offset_bounds = Rectangle::new(offset_position, offset_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 {
|
||||
child_positions,
|
||||
|
|
@ -445,13 +445,14 @@ where
|
|||
pub(crate) root_bounds_list: Vec<Rectangle>,
|
||||
pub(crate) path_highlight: Option<PathHighlight>,
|
||||
pub(crate) style: &'b <crate::Theme as StyleSheet>::Style,
|
||||
pub(crate) position: Point,
|
||||
}
|
||||
impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::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>
|
||||
|
|
@ -459,14 +460,9 @@ impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer
|
|||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_translation: iced::Vector,
|
||||
) -> Node {
|
||||
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
|
||||
// layout children
|
||||
let position = self.position;
|
||||
let state = self.tree.state.downcast_mut::<MenuBarState>();
|
||||
let overlay_offset = Point::ORIGIN - position;
|
||||
let tree_children = &mut self.tree.children;
|
||||
|
|
|
|||
|
|
@ -146,14 +146,14 @@ pub fn menu_button<'a, Message: 'a>(
|
|||
) -> crate::widget::Button<'a, Message> {
|
||||
widget::button::custom(
|
||||
widget::Row::with_children(children)
|
||||
.align_items(Alignment::Center)
|
||||
.align_y(Alignment::Center)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.height(Length::Fixed(36.0))
|
||||
.padding([4, 16])
|
||||
.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.
|
||||
|
|
@ -197,7 +197,7 @@ where
|
|||
{
|
||||
widget::button::custom(widget::text(label))
|
||||
.padding([4, 12])
|
||||
.style(theme::Button::MenuRoot)
|
||||
.class(theme::Button::MenuRoot)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ where
|
|||
let key = find_key(&action, key_binds);
|
||||
let menu_button = menu_button(vec![
|
||||
widget::text(label).into(),
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
widget::horizontal_space().width(Length::Fill).into(),
|
||||
widget::text(key).into(),
|
||||
])
|
||||
.on_press(action.message());
|
||||
|
|
@ -256,7 +256,7 @@ where
|
|||
let key = find_key(&action, key_binds);
|
||||
let menu_button = menu_button(vec![
|
||||
widget::text(label).into(),
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
widget::horizontal_space().width(Length::Fill).into(),
|
||||
widget::text(key).into(),
|
||||
]);
|
||||
|
||||
|
|
@ -270,8 +270,8 @@ where
|
|||
widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.style(theme::Svg::Custom(Rc::new(|theme| {
|
||||
crate::iced_style::svg::Appearance {
|
||||
.class(theme::Svg::Custom(Rc::new(|theme| {
|
||||
iced_widget::svg::Style {
|
||||
color: Some(theme.cosmic().accent_color().into()),
|
||||
}
|
||||
})))
|
||||
|
|
@ -282,9 +282,9 @@ where
|
|||
},
|
||||
widget::Space::with_width(Length::Fixed(8.0)).into(),
|
||||
widget::text(label)
|
||||
.horizontal_alignment(iced::alignment::Horizontal::Left)
|
||||
.align_x(iced::alignment::Horizontal::Left)
|
||||
.into(),
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
widget::horizontal_space().width(Length::Fill).into(),
|
||||
widget::text(key).into(),
|
||||
])
|
||||
.on_press(action.message()),
|
||||
|
|
@ -294,13 +294,13 @@ where
|
|||
trees.push(MenuTree::<Message, Renderer>::with_children(
|
||||
menu_button(vec![
|
||||
widget::text(label).into(),
|
||||
widget::horizontal_space(Length::Fill).into(),
|
||||
widget::horizontal_space().width(Length::Fill).into(),
|
||||
widget::icon::from_name("pan-end-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.into(),
|
||||
])
|
||||
.style(
|
||||
.class(
|
||||
// Menu folders have no on_press so they take on the disabled style by default
|
||||
if children.is_empty() {
|
||||
// This will make the folder use the disabled style if it has no children
|
||||
|
|
|
|||
319
src/widget/min_size_tracker/mod.rs
Normal file
319
src/widget/min_size_tracker/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
88
src/widget/min_size_tracker/subscription.rs
Normal file
88
src/widget/min_size_tracker/subscription.rs
Normal 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>),
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ pub use iced::widget::{combo_box, ComboBox};
|
|||
pub use iced::widget::{container, Container};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use iced::widget::{horizontal_space, space, vertical_space, Space};
|
||||
pub use iced::widget::{horizontal_space, vertical_space, Space};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use iced::widget::{image, Image};
|
||||
|
|
@ -94,6 +94,9 @@ pub use iced_core::widget::{Id, Operation, Widget};
|
|||
|
||||
pub mod aspect_ratio;
|
||||
|
||||
#[cfg(feature = "autosize")]
|
||||
pub mod autosize;
|
||||
|
||||
pub mod button;
|
||||
#[doc(inline)]
|
||||
pub use button::{Button, IconButton, LinkButton, TextButton};
|
||||
|
|
@ -166,20 +169,20 @@ pub mod divider {
|
|||
|
||||
/// Horizontal divider with default thickness
|
||||
#[must_use]
|
||||
pub fn default() -> Rule<crate::Theme> {
|
||||
horizontal_rule(1).style(crate::theme::Rule::Default)
|
||||
pub fn default<'a>() -> Rule<'a, crate::Theme> {
|
||||
horizontal_rule(1).class(crate::theme::Rule::Default)
|
||||
}
|
||||
|
||||
/// Horizontal divider with light thickness
|
||||
#[must_use]
|
||||
pub fn light() -> Rule<crate::Theme> {
|
||||
horizontal_rule(1).style(crate::theme::Rule::LightDivider)
|
||||
pub fn light<'a>() -> Rule<'a, crate::Theme> {
|
||||
horizontal_rule(1).class(crate::theme::Rule::LightDivider)
|
||||
}
|
||||
|
||||
/// Horizontal divider with heavy thickness.
|
||||
#[must_use]
|
||||
pub fn heavy() -> Rule<crate::Theme> {
|
||||
horizontal_rule(4).style(crate::theme::Rule::HeavyDivider)
|
||||
pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
|
||||
horizontal_rule(4).class(crate::theme::Rule::HeavyDivider)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,20 +192,20 @@ pub mod divider {
|
|||
|
||||
/// Vertical divider with default thickness
|
||||
#[must_use]
|
||||
pub fn default() -> Rule<crate::Theme> {
|
||||
vertical_rule(1).style(crate::theme::Rule::Default)
|
||||
pub fn default<'a>() -> Rule<'a, crate::Theme> {
|
||||
vertical_rule(1).class(crate::theme::Rule::Default)
|
||||
}
|
||||
|
||||
/// Vertical divider with light thickness
|
||||
#[must_use]
|
||||
pub fn light() -> Rule<crate::Theme> {
|
||||
vertical_rule(4).style(crate::theme::Rule::LightDivider)
|
||||
pub fn light<'a>() -> Rule<'a, crate::Theme> {
|
||||
vertical_rule(4).class(crate::theme::Rule::LightDivider)
|
||||
}
|
||||
|
||||
/// Vertical divider with heavy thickness.
|
||||
#[must_use]
|
||||
pub fn heavy() -> Rule<crate::Theme> {
|
||||
vertical_rule(10).style(crate::theme::Rule::HeavyDivider)
|
||||
pub fn heavy<'a>() -> Rule<'a, crate::Theme> {
|
||||
vertical_rule(10).class(crate::theme::Rule::HeavyDivider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -268,7 +271,7 @@ pub use radio::{radio, Radio};
|
|||
|
||||
pub mod rectangle_tracker;
|
||||
#[doc(inline)]
|
||||
pub use rectangle_tracker::{rectangle_tracker, RectangleTracker};
|
||||
pub use rectangle_tracker::{rectangle_tracking_container, RectangleTracker};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use row::{row, Row};
|
||||
|
|
@ -343,13 +346,13 @@ pub mod tooltip {
|
|||
|
||||
pub fn tooltip<'a, Message>(
|
||||
content: impl Into<Element<'a, Message>>,
|
||||
tooltip: impl Into<Cow<'a, str>>,
|
||||
tooltip: impl Into<Element<'a, Message>>,
|
||||
position: Position,
|
||||
) -> Tooltip<'a, Message> {
|
||||
let xxs = crate::theme::active().cosmic().space_xxs();
|
||||
|
||||
Tooltip::new(content, tooltip, position)
|
||||
.style(crate::theme::Container::Tooltip)
|
||||
.class(crate::theme::Container::Tooltip)
|
||||
.padding(xxs)
|
||||
.gap(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,11 +147,12 @@ impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>>
|
|||
.spacing(space_xxs)
|
||||
.style(crate::theme::SegmentedButton::TabBar)
|
||||
.apply(scrollable)
|
||||
.class(crate::style::iced::Scrollable::Minimal)
|
||||
.height(Length::Fill)
|
||||
.apply(container)
|
||||
.padding(space_xxs)
|
||||
.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]
|
||||
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();
|
||||
iced_style::container::Appearance {
|
||||
iced_widget::container::Style {
|
||||
icon_color: Some(cosmic.on_bg_color().into()),
|
||||
text_color: Some(cosmic.on_bg_color().into()),
|
||||
background: Some(Background::Color(cosmic.primary.base.into())),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub struct NavBarToggle<Message> {
|
|||
active: bool,
|
||||
#[setters(strip_option)]
|
||||
on_toggle: Option<Message>,
|
||||
style: crate::theme::Button,
|
||||
class: crate::theme::Button,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ pub fn nav_bar_toggle<Message>() -> NavBarToggle<Message> {
|
|||
NavBarToggle {
|
||||
active: false,
|
||||
on_toggle: None,
|
||||
style: crate::theme::Button::Text,
|
||||
class: crate::theme::Button::Text,
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ impl<'a, Message: 'static + Clone> From<NavBarToggle<Message>> for Element<'a, M
|
|||
.padding([8, 16])
|
||||
.on_press_maybe(nav_bar_toggle.on_toggle)
|
||||
.selected(nav_bar_toggle.selected)
|
||||
.style(nav_bar_toggle.style)
|
||||
.class(nav_bar_toggle.class)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ use iced_core::mouse;
|
|||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::touch;
|
||||
use iced_core::widget::{tree, Operation, OperationOutputWrapper, Tree};
|
||||
use iced_core::widget::{tree, Operation, Tree};
|
||||
use iced_core::{
|
||||
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>(
|
||||
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
||||
|
|
@ -123,7 +123,7 @@ where
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
|
|
@ -214,6 +214,7 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
mut translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
if !tree.state.downcast_mut::<State>().is_open {
|
||||
return None;
|
||||
|
|
@ -239,19 +240,22 @@ where
|
|||
// Round position to prevent rendering issues
|
||||
overlay_position.x = overlay_position.x.round();
|
||||
overlay_position.y = overlay_position.y.round();
|
||||
translation.x += overlay_position.x;
|
||||
translation.y += overlay_position.y;
|
||||
|
||||
Some(overlay::Element::new(
|
||||
overlay_position,
|
||||
Box::new(Overlay {
|
||||
tree: &mut tree.children[1],
|
||||
content: popup,
|
||||
position: self.position,
|
||||
}),
|
||||
))
|
||||
Some(overlay::Element::new(Box::new(Overlay {
|
||||
tree: &mut tree.children[1],
|
||||
content: popup,
|
||||
position: self.position,
|
||||
pos: Point::new(translation.x, translation.y),
|
||||
})))
|
||||
} else {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer)
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +290,7 @@ pub struct Overlay<'a, 'b, Message, Renderer> {
|
|||
tree: &'a mut Tree,
|
||||
content: &'a mut Element<'b, Message, crate::Theme, Renderer>,
|
||||
position: Position,
|
||||
pos: Point,
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
|
||||
|
|
@ -294,13 +299,8 @@ where
|
|||
Message: Clone,
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
mut position: Point,
|
||||
_translation: iced::Vector,
|
||||
) -> layout::Node {
|
||||
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
|
||||
let mut position = self.pos;
|
||||
let limits = layout::Limits::new(Size::UNIT, bounds);
|
||||
let node = self
|
||||
.content
|
||||
|
|
@ -342,7 +342,7 @@ where
|
|||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
|
|
@ -413,7 +413,7 @@ where
|
|||
) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut self.tree, layout, renderer)
|
||||
.overlay(&mut self.tree, layout, renderer, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
//! Create choices using radio buttons.
|
||||
use crate::Theme;
|
||||
use iced::border;
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
|
|
@ -7,17 +9,18 @@ use iced_core::renderer;
|
|||
use iced_core::touch;
|
||||
use iced_core::widget::tree::Tree;
|
||||
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>>,
|
||||
value: V,
|
||||
selected: Option<V>,
|
||||
f: F,
|
||||
) -> Radio<'a, Message, Theme, crate::Renderer>
|
||||
) -> Radio<'a, Message, crate::Renderer>
|
||||
where
|
||||
V: Eq + Copy,
|
||||
F: FnOnce(V) -> Message,
|
||||
|
|
@ -83,9 +86,8 @@ where
|
|||
/// let content = column![a, b, c, all];
|
||||
/// ```
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
|
||||
pub struct Radio<'a, Message, Renderer = crate::Renderer>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
is_selected: bool,
|
||||
|
|
@ -94,13 +96,11 @@ where
|
|||
width: Length,
|
||||
size: 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
|
||||
Message: Clone,
|
||||
Theme: StyleSheet,
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
/// The default size of a [`Radio`] button.
|
||||
|
|
@ -130,7 +130,6 @@ where
|
|||
width: Length::Shrink,
|
||||
size: Self::DEFAULT_SIZE,
|
||||
spacing: Self::DEFAULT_SPACING,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,20 +153,11 @@ where
|
|||
self.spacing = spacing.into().0;
|
||||
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>
|
||||
for Radio<'a, Message, Theme, Renderer>
|
||||
impl<'a, Message, Renderer> Widget<Message, Theme, Renderer> for Radio<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Theme: StyleSheet,
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
|
|
@ -207,9 +197,7 @@ where
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.label.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
|
|
@ -302,9 +290,19 @@ where
|
|||
let mut children = layout.children();
|
||||
|
||||
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 {
|
||||
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,
|
||||
height: dot_size,
|
||||
},
|
||||
border: Border::with_radius(dot_size / 2.0),
|
||||
border: border::rounded(dot_size / 2.0),
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
custom_style.dot_color,
|
||||
|
|
@ -363,11 +361,13 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.label.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().nth(1).unwrap(),
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +376,7 @@ where
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.label.as_widget().drag_destinations(
|
||||
&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>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Theme: 'a + StyleSheet,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod subscription;
|
|||
|
||||
use iced::futures::channel::mpsc::UnboundedSender;
|
||||
use iced::widget::Container;
|
||||
use iced::Vector;
|
||||
pub use subscription::*;
|
||||
|
||||
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 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,
|
||||
id: I,
|
||||
tx: UnboundedSender<(I, Rectangle)>,
|
||||
|
|
@ -71,6 +72,7 @@ where
|
|||
id: I,
|
||||
container: Container<'a, Message, crate::Theme, Renderer>,
|
||||
ignore_bounds: bool,
|
||||
request_size: bool,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, I> RectangleTrackingContainer<'a, Message, Renderer, I>
|
||||
|
|
@ -88,6 +90,7 @@ where
|
|||
tx,
|
||||
container: Container::new(content),
|
||||
ignore_bounds: false,
|
||||
request_size: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,22 +149,22 @@ where
|
|||
|
||||
/// Centers the contents in the horizontal axis of the [`Container`].
|
||||
#[must_use]
|
||||
pub fn center_x(mut self) -> Self {
|
||||
self.container = self.container.center_x();
|
||||
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) -> Self {
|
||||
self.container = self.container.center_y();
|
||||
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 StyleSheet>::Style>) -> Self {
|
||||
self.container = self.container.style(style);
|
||||
pub fn style(mut self, style: impl Into<<crate::Theme as Catalog>::Class<'a>>) -> Self {
|
||||
self.container = self.container.class(style);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +205,7 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.container.layout(
|
||||
let layout = self.container.layout(
|
||||
tree,
|
||||
renderer,
|
||||
if self.ignore_bounds {
|
||||
|
|
@ -210,7 +213,10 @@ where
|
|||
} else {
|
||||
limits
|
||||
},
|
||||
)
|
||||
);
|
||||
if self.request_size {}
|
||||
|
||||
layout
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -218,9 +224,7 @@ where
|
|||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
self.container.operate(tree, layout, renderer, operation);
|
||||
}
|
||||
|
|
@ -271,7 +275,6 @@ where
|
|||
viewport: &Rectangle,
|
||||
) {
|
||||
let _ = self.tx.unbounded_send((self.id, layout.bounds()));
|
||||
|
||||
self.container.draw(
|
||||
tree,
|
||||
renderer,
|
||||
|
|
@ -288,8 +291,9 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
self.container.overlay(tree, layout, renderer)
|
||||
self.container.overlay(tree, layout, renderer, translation)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -297,7 +301,7 @@ where
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.container
|
||||
.drag_destinations(state, layout, renderer, dnd_rectangles);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use iced::{
|
||||
futures::{
|
||||
channel::mpsc::{unbounded, UnboundedReceiver},
|
||||
StreamExt,
|
||||
stream, StreamExt,
|
||||
},
|
||||
subscription, Rectangle,
|
||||
Rectangle,
|
||||
};
|
||||
use iced_futures::Subscription;
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
||||
|
||||
use super::RectangleTracker;
|
||||
|
|
@ -14,8 +15,11 @@ pub fn rectangle_tracker_subscription<
|
|||
R: 'static + Hash + Copy + Send + Sync + Debug + Eq,
|
||||
>(
|
||||
id: I,
|
||||
) -> iced::Subscription<(I, RectangleUpdate<R>)> {
|
||||
subscription::unfold(id, State::Ready, move |state| start_listening(id, state))
|
||||
) -> Subscription<(I, RectangleUpdate<R>)> {
|
||||
Subscription::run_with_id(
|
||||
id,
|
||||
stream::unfold(State::Ready, move |state| start_listening(id, state)),
|
||||
)
|
||||
}
|
||||
|
||||
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>(
|
||||
id: I,
|
||||
mut state: State<R>,
|
||||
) -> ((I, RectangleUpdate<R>), State<R>) {
|
||||
) -> Option<((I, RectangleUpdate<R>), State<R>)> {
|
||||
loop {
|
||||
let (update, new_state) = match state {
|
||||
State::Ready => {
|
||||
|
|
@ -65,11 +69,11 @@ async fn start_listening<I: Copy, R: 'static + Hash + Copy + Send + Sync + Debug
|
|||
}
|
||||
None => (None, State::Finished),
|
||||
},
|
||||
State::Finished => iced::futures::future::pending().await,
|
||||
State::Finished => return None,
|
||||
};
|
||||
state = new_state;
|
||||
if let Some(u) = update {
|
||||
return (u, state);
|
||||
return Some((u, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@ use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, O
|
|||
use iced::clipboard::mime::AllowedMimeTypes;
|
||||
use iced::touch::Finger;
|
||||
use iced::{
|
||||
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length,
|
||||
Padding, Rectangle, Size,
|
||||
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Event, Length, Padding,
|
||||
Rectangle, Size, Task, Vector,
|
||||
};
|
||||
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::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
|
||||
use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text};
|
||||
use iced_runtime::{task, Action};
|
||||
use slotmap::{Key, SecondaryMap};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
|
|
@ -34,8 +35,8 @@ use std::mem;
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
/// A command that focuses a segmented item stored in a widget.
|
||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::focusable::focus(id.0))
|
||||
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
|
||||
task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0))))
|
||||
}
|
||||
|
||||
pub enum ItemBounds {
|
||||
|
|
@ -436,15 +437,15 @@ where
|
|||
if !text.is_empty() {
|
||||
icon_spacing = f32::from(self.button_spacing);
|
||||
let paragraph = entry.or_insert_with(|| {
|
||||
crate::Paragraph::with_text(Text {
|
||||
content: text,
|
||||
crate::Plain::new(Text {
|
||||
content: text.as_ref(),
|
||||
size: iced::Pixels(self.font_size),
|
||||
bounds: Size::INFINITY,
|
||||
font,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: Shaping::Advanced,
|
||||
wrap: Wrap::default(),
|
||||
wrapping: Wrapping::default(),
|
||||
line_height: self.line_height,
|
||||
})
|
||||
});
|
||||
|
|
@ -622,23 +623,21 @@ where
|
|||
}
|
||||
|
||||
let text = Text {
|
||||
content: text,
|
||||
content: text.as_ref(),
|
||||
size: iced::Pixels(self.font_size),
|
||||
bounds: Size::INFINITY,
|
||||
font,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: Shaping::Advanced,
|
||||
wrap: Wrap::default(),
|
||||
wrapping: Wrapping::default(),
|
||||
line_height: self.line_height,
|
||||
};
|
||||
|
||||
if let Some(paragraph) = state.paragraphs.get_mut(key) {
|
||||
paragraph.update(text);
|
||||
} else {
|
||||
state
|
||||
.paragraphs
|
||||
.insert(key, crate::Paragraph::with_text(text));
|
||||
state.paragraphs.insert(key, crate::Plain::new(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1079,9 +1078,7 @@ where
|
|||
tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<
|
||||
iced_core::widget::OperationOutputWrapper<Message>,
|
||||
>,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
operation.focusable(state, Some(&self.id.0));
|
||||
|
|
@ -1492,7 +1489,7 @@ where
|
|||
if self.model.text(key).is_some_and(|text| !text.is_empty()) {
|
||||
// Draw the text for this segmented button or tab.
|
||||
renderer.fill_paragraph(
|
||||
&state.paragraphs[key],
|
||||
state.paragraphs[key].raw(),
|
||||
bounds.position(),
|
||||
apply_alpha(status_appearance.text_color),
|
||||
Rectangle {
|
||||
|
|
@ -1528,6 +1525,7 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
let state = tree.state.downcast_ref::<LocalState>();
|
||||
|
||||
|
|
@ -1575,6 +1573,7 @@ where
|
|||
root_bounds_list: vec![bounds],
|
||||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
style: &crate::theme::menu_bar::MenuBarStyle::Default,
|
||||
position: Point::new(translation.x, translation.y),
|
||||
}
|
||||
.overlay(),
|
||||
)
|
||||
|
|
@ -1645,7 +1644,7 @@ pub struct LocalState {
|
|||
/// Dimensions of internal buttons when shrinking
|
||||
pub(super) internal_layout: Vec<(Size, Size)>,
|
||||
/// The paragraphs for each text.
|
||||
paragraphs: SecondaryMap<Entity, crate::Paragraph>,
|
||||
paragraphs: SecondaryMap<Entity, crate::Plain>,
|
||||
/// Used to detect changes in text.
|
||||
text_hashes: SecondaryMap<Entity, u64>,
|
||||
/// Location of cursor when context menu was opened.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
Element,
|
||||
};
|
||||
use derive_setters::Setters;
|
||||
use iced_core::{text::Wrap, Length};
|
||||
use iced_core::{text::Wrapping, Length};
|
||||
use taffy::AlignContent;
|
||||
|
||||
/// A settings item aligned in a row
|
||||
|
|
@ -20,8 +20,8 @@ pub fn item<'a, Message: 'static>(
|
|||
widget: impl Into<Element<'a, Message>> + 'a,
|
||||
) -> Row<'a, Message> {
|
||||
item_row(vec![
|
||||
text(title).wrap(Wrap::Word).into(),
|
||||
horizontal_space(iced::Length::Fill).into(),
|
||||
text(title).wrapping(Wrapping::Word).into(),
|
||||
horizontal_space().width(iced::Length::Fill).into(),
|
||||
widget.into(),
|
||||
])
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
|
|||
} = theme::THEME.lock().unwrap().cosmic().spacing;
|
||||
row::with_children(children)
|
||||
.spacing(space_xs)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.padding([0, space_s])
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +46,10 @@ pub fn flex_item<'a, Message: 'static>(
|
|||
widget: impl Into<Element<'a, Message>> + 'a,
|
||||
) -> FlexRow<'a, Message> {
|
||||
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(),
|
||||
])
|
||||
}
|
||||
|
|
@ -111,8 +114,8 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
|||
if let Some(description) = self.description {
|
||||
let column = column::with_capacity(2)
|
||||
.spacing(2)
|
||||
.push(text(self.title).wrap(Wrap::Word))
|
||||
.push(text(description).wrap(Wrap::Word).size(10))
|
||||
.push(text(self.title).wrapping(Wrapping::Word))
|
||||
.push(text(description).wrapping(Wrapping::Word).size(10))
|
||||
.width(Length::Fill);
|
||||
|
||||
contents.push(column.into());
|
||||
|
|
@ -129,6 +132,6 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
|||
is_checked: bool,
|
||||
message: impl Fn(bool) -> Message + 'static,
|
||||
) -> Row<'a, Message> {
|
||||
self.control(crate::widget::toggler(None, is_checked, message))
|
||||
self.control(crate::widget::toggler(is_checked, message))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,11 +56,10 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
|
|||
.apply(button::custom)
|
||||
.width(Length::Fixed(32.0))
|
||||
.height(Length::Fixed(32.0))
|
||||
.style(theme::Button::Text)
|
||||
.class(theme::Button::Text)
|
||||
.on_press(model::Message::Decrement)
|
||||
.into(),
|
||||
text::title4(label)
|
||||
.vertical_alignment(Vertical::Center)
|
||||
.apply(container)
|
||||
.width(Length::Fixed(48.0))
|
||||
.align_x(Horizontal::Center)
|
||||
|
|
@ -76,18 +75,18 @@ impl<'a, Message: 'static> SpinButton<'a, Message> {
|
|||
.apply(button::custom)
|
||||
.width(Length::Fixed(32.0))
|
||||
.height(Length::Fixed(32.0))
|
||||
.style(theme::Button::Text)
|
||||
.class(theme::Button::Text)
|
||||
.on_press(model::Message::Increment)
|
||||
.into(),
|
||||
])
|
||||
.width(Length::Shrink)
|
||||
.height(Length::Fixed(32.0))
|
||||
.align_items(Alignment::Center),
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.align_y(Vertical::Center)
|
||||
.width(Length::Shrink)
|
||||
.height(Length::Fixed(32.0))
|
||||
.style(theme::Container::custom(container_style))
|
||||
.class(theme::Container::custom(container_style))
|
||||
.apply(Element::from)
|
||||
.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)]
|
||||
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 mut neutral_10 = basic.palette.neutral_10;
|
||||
neutral_10.alpha = 0.1;
|
||||
let accent = &basic.accent;
|
||||
let corners = &basic.corner_radii;
|
||||
iced_style::container::Appearance {
|
||||
iced_widget::container::Style {
|
||||
icon_color: Some(basic.palette.neutral_10.into()),
|
||||
text_color: Some(basic.palette.neutral_10.into()),
|
||||
background: None,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// [`Text`]: widget::Text
|
||||
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
|
||||
|
|
@ -26,7 +26,7 @@ pub enum Typography {
|
|||
|
||||
/// [`Text`] widget with the Title 1 typography preset.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(44.0.into()))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(36.0.into()))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(32.0.into()))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(28.0.into()))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(iced::Pixels(20.0)))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(iced::Pixels(14.0)))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(20.0.into()))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(14.0.into()))
|
||||
.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.
|
||||
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)
|
||||
.line_height(LineHeight::Absolute(20.0.into()))
|
||||
.font(crate::font::mono())
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ use super::style::StyleSheet;
|
|||
pub use super::value::Value;
|
||||
|
||||
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_core::event::{self, Event};
|
||||
use iced_core::mouse::{self, click};
|
||||
|
|
@ -39,15 +42,9 @@ use iced_core::{
|
|||
};
|
||||
#[cfg(feature = "wayland")]
|
||||
use iced_renderer::core::event::{wayland, PlatformSpecific};
|
||||
use iced_renderer::core::widget::OperationOutputWrapper;
|
||||
#[cfg(feature = "wayland")]
|
||||
use iced_runtime::command::platform_specific;
|
||||
use iced_runtime::Command;
|
||||
|
||||
#[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};
|
||||
use iced_runtime::platform_specific;
|
||||
use iced_runtime::{task, Action, Task};
|
||||
|
||||
thread_local! {
|
||||
// Prevents two inputs from being focused at the same time.
|
||||
|
|
@ -146,7 +143,7 @@ where
|
|||
})
|
||||
.size(16)
|
||||
.apply(crate::widget::button::custom)
|
||||
.style(crate::theme::Button::Icon)
|
||||
.class(crate::theme::Button::Icon)
|
||||
.on_press(msg)
|
||||
.padding(8)
|
||||
.into(),
|
||||
|
|
@ -173,7 +170,6 @@ where
|
|||
.padding(spacing)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
|
||||
"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",
|
||||
];
|
||||
#[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.
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[must_use]
|
||||
pub struct TextInput<'a, Message> {
|
||||
id: Option<Id>,
|
||||
id: Id,
|
||||
placeholder: Cow<'a, str>,
|
||||
value: Value,
|
||||
is_secure: bool,
|
||||
|
|
@ -215,7 +206,6 @@ pub struct TextInput<'a, Message> {
|
|||
trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
||||
style: <crate::Theme as StyleSheet>::Style,
|
||||
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)>,
|
||||
dnd_icon: bool,
|
||||
line_height: text::LineHeight,
|
||||
|
|
@ -237,7 +227,7 @@ where
|
|||
|
||||
let v: Cow<'a, str> = value.into();
|
||||
TextInput {
|
||||
id: None,
|
||||
id: Id::unique(),
|
||||
placeholder: placeholder.into(),
|
||||
value: Value::new(v.as_ref()),
|
||||
is_secure: false,
|
||||
|
|
@ -258,7 +248,6 @@ where
|
|||
trailing_icon: None,
|
||||
error: None,
|
||||
style: crate::theme::TextInput::default(),
|
||||
on_dnd_command_produced: None,
|
||||
on_create_dnd_source: None,
|
||||
surface_ids: None,
|
||||
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.
|
||||
/// This makes it behave as if it was always focused.
|
||||
pub fn always_active(mut self) -> Self {
|
||||
|
|
@ -290,7 +288,7 @@ where
|
|||
|
||||
/// Sets the [`Id`] of the [`TextInput`].
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = Some(id);
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -433,10 +431,12 @@ where
|
|||
value: Option<&Value>,
|
||||
style: &renderer::Style,
|
||||
) {
|
||||
let text_layout = self.text_layout(layout.clone());
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
text_layout,
|
||||
cursor_position,
|
||||
tree,
|
||||
value.unwrap_or(&self.value),
|
||||
|
|
@ -467,20 +467,6 @@ where
|
|||
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.
|
||||
/// Both ids are required to be unique.
|
||||
/// This is required for the dnd to work.
|
||||
|
|
@ -500,7 +486,7 @@ where
|
|||
crate::widget::icon::from_name("edit-clear-symbolic")
|
||||
.size(16)
|
||||
.apply(crate::widget::button::custom)
|
||||
.style(crate::theme::Button::Icon)
|
||||
.class(crate::theme::Button::Icon)
|
||||
.on_press(on_clear)
|
||||
.padding(8)
|
||||
.into(),
|
||||
|
|
@ -550,6 +536,7 @@ where
|
|||
}
|
||||
let old_value = state
|
||||
.value
|
||||
.raw()
|
||||
.buffer()
|
||||
.lines
|
||||
.iter()
|
||||
|
|
@ -559,6 +546,7 @@ where
|
|||
|| old_value != self.value.to_string()
|
||||
|| state
|
||||
.label
|
||||
.raw()
|
||||
.buffer()
|
||||
.lines
|
||||
.iter()
|
||||
|
|
@ -567,6 +555,7 @@ where
|
|||
!= self.label.unwrap_or_default()
|
||||
|| state
|
||||
.helper_text
|
||||
.raw()
|
||||
.buffer()
|
||||
.lines
|
||||
.iter()
|
||||
|
|
@ -651,7 +640,7 @@ where
|
|||
let v = self.value.to_string();
|
||||
value_paragraph.update(Text {
|
||||
content: if self.value.is_empty() {
|
||||
&self.placeholder
|
||||
self.placeholder.as_ref()
|
||||
} else {
|
||||
&v
|
||||
},
|
||||
|
|
@ -662,7 +651,7 @@ where
|
|||
vertical_alignment: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
});
|
||||
|
||||
let Size { width, height } =
|
||||
|
|
@ -717,12 +706,13 @@ where
|
|||
tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
operation.focusable(state, self.id.as_ref());
|
||||
operation.text_input(state, self.id.as_ref());
|
||||
operation.custom(state, Some(&self.id));
|
||||
operation.focusable(state, Some(&self.id));
|
||||
operation.text_input(state, Some(&self.id));
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
|
|
@ -730,6 +720,7 @@ where
|
|||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
let mut layout_ = Vec::with_capacity(2);
|
||||
if self.leading_icon.is_some() {
|
||||
|
|
@ -752,7 +743,9 @@ where
|
|||
.zip(&mut tree.children)
|
||||
.zip(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<_>>();
|
||||
|
||||
|
|
@ -814,8 +807,10 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dnd_id = self.dnd_id();
|
||||
let id = Widget::id(self);
|
||||
update(
|
||||
id,
|
||||
event,
|
||||
text_layout.children().next().unwrap(),
|
||||
trailing_icon_layout,
|
||||
|
|
@ -833,9 +828,7 @@ where
|
|||
self.on_toggle_edit.as_deref(),
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
self.on_create_dnd_source.as_deref(),
|
||||
self.dnd_icon,
|
||||
self.on_dnd_command_produced.as_deref(),
|
||||
self.surface_ids,
|
||||
dnd_id,
|
||||
line_height,
|
||||
layout,
|
||||
)
|
||||
|
|
@ -851,10 +844,12 @@ where
|
|||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let text_layout = self.text_layout(layout.clone());
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
text_layout,
|
||||
cursor_position,
|
||||
tree,
|
||||
&self.value,
|
||||
|
|
@ -934,11 +929,43 @@ where
|
|||
}
|
||||
|
||||
fn id(&self) -> Option<Id> {
|
||||
self.id.clone()
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
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`].
|
||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::focusable::focus(id))
|
||||
/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
|
||||
pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
|
||||
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.
|
||||
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::text_input::move_cursor_to_end(id))
|
||||
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
|
||||
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.
|
||||
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::text_input::move_cursor_to_front(id))
|
||||
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
|
||||
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.
|
||||
pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Command<Message> {
|
||||
Command::widget(operation::text_input::move_cursor_to(id, position))
|
||||
pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
|
||||
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`].
|
||||
pub fn select_all<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::text_input::select_all(id))
|
||||
/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
|
||||
pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
|
||||
task::effect(Action::widget(operation::text_input::select_all(id)))
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`TextInput`].
|
||||
|
|
@ -1019,7 +1052,7 @@ pub fn layout<Message>(
|
|||
vertical_alignment: alignment::Vertical::Center,
|
||||
line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
});
|
||||
let label_size = label_paragraph.min_bounds();
|
||||
|
||||
|
|
@ -1158,7 +1191,7 @@ pub fn layout<Message>(
|
|||
vertical_alignment: alignment::Vertical::Center,
|
||||
line_height: helper_text_line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
});
|
||||
let helper_text_size = helper_text_paragraph.min_bounds();
|
||||
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::cast_lossless)]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn update<'a, Message>(
|
||||
pub fn update<'a, Message: 'static>(
|
||||
id: Option<Id>,
|
||||
event: Event,
|
||||
text_layout: Layout<'_>,
|
||||
trailing_icon_layout: Option<Layout<'_>>,
|
||||
|
|
@ -1206,9 +1240,7 @@ pub fn update<'a, Message>(
|
|||
on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
|
||||
#[allow(unused_variables)] dnd_icon: bool,
|
||||
#[allow(unused_variables)] on_dnd_command_produced: Option<&dyn Fn(DnDCommand) -> Message>,
|
||||
#[allow(unused_variables)] surface_ids: Option<(window::Id, window::Id)>,
|
||||
#[allow(unused_variables)] dnd_id: u128,
|
||||
line_height: text::LineHeight,
|
||||
layout: Layout<'_>,
|
||||
) -> event::Status
|
||||
|
|
@ -1253,7 +1285,8 @@ where
|
|||
let text_layout = layout.children().next().unwrap();
|
||||
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 (
|
||||
&state.dragging_state,
|
||||
|
|
@ -1266,28 +1299,18 @@ where
|
|||
// single click that is on top of the selected text
|
||||
// is the click on selected text?
|
||||
|
||||
if let (
|
||||
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,
|
||||
) {
|
||||
if let Some(on_input) = on_input {
|
||||
let left = start.min(end);
|
||||
let right = end.max(start);
|
||||
|
||||
let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
|
||||
&state.value,
|
||||
state.value.raw(),
|
||||
text_layout.bounds(),
|
||||
left,
|
||||
);
|
||||
|
||||
let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
|
||||
&state.value,
|
||||
state.value.raw(),
|
||||
text_layout.bounds(),
|
||||
right,
|
||||
);
|
||||
|
|
@ -1305,10 +1328,12 @@ where
|
|||
if is_secure {
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
let text =
|
||||
let input_text =
|
||||
state.selected_text(&value.to_string()).unwrap_or_default();
|
||||
state.dragging_state =
|
||||
Some(DraggingState::Dnd(DndAction::empty(), text.clone()));
|
||||
state.dragging_state = Some(DraggingState::Dnd(
|
||||
DndAction::empty(),
|
||||
input_text.clone(),
|
||||
));
|
||||
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
|
||||
editor.delete();
|
||||
|
||||
|
|
@ -1316,23 +1341,25 @@ where
|
|||
let unsecured_value = Value::new(&contents);
|
||||
let message = (on_input)(contents);
|
||||
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();
|
||||
shell.publish(on_dnd_command_produced(Box::new(move || {
|
||||
platform_specific::wayland::data_device::ActionInner::StartDnd {
|
||||
mime_types: SUPPORTED_TEXT_MIME_TYPES
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect(),
|
||||
actions: DndAction::Move,
|
||||
origin_id: window_id,
|
||||
icon_id: Some((
|
||||
DndIcon::Widget(icon_id, Box::new(state_clone.clone())),
|
||||
iced::Vector::ZERO,
|
||||
)),
|
||||
data: Box::new(TextInputString(text.clone())),
|
||||
}
|
||||
})));
|
||||
|
||||
iced_core::clipboard::start_dnd(
|
||||
clipboard,
|
||||
false,
|
||||
id.map(|id| iced_core::clipboard::DndSource::Widget(id)),
|
||||
Some((
|
||||
Element::from(
|
||||
TextInput::<'static, ()>::new("", input_text.clone())
|
||||
.dnd_icon(true),
|
||||
),
|
||||
iced_core::widget::tree::State::new(state_clone),
|
||||
)),
|
||||
Box::new(TextInputString(input_text)),
|
||||
DndAction::Move,
|
||||
);
|
||||
|
||||
update_cache(state, &unsecured_value);
|
||||
} else {
|
||||
|
|
@ -1435,7 +1462,7 @@ where
|
|||
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 (
|
||||
&state.dragging_state,
|
||||
|
|
@ -1621,7 +1648,10 @@ where
|
|||
{
|
||||
if !is_secure {
|
||||
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 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);
|
||||
|
|
@ -1650,7 +1683,7 @@ where
|
|||
content
|
||||
} else {
|
||||
let content: String = clipboard
|
||||
.read()
|
||||
.read(iced_core::clipboard::Kind::Primary)
|
||||
.unwrap_or_default()
|
||||
.chars()
|
||||
.filter(|c| !c.is_control())
|
||||
|
|
@ -1760,7 +1793,7 @@ where
|
|||
|
||||
state.keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::Window(_, window::Event::RedrawRequested(now)) => {
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
|
|
@ -1775,9 +1808,7 @@ where
|
|||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource(
|
||||
wayland::DataSourceEvent::DndFinished | wayland::DataSourceEvent::Cancelled,
|
||||
))) => {
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
|
||||
let state = state();
|
||||
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
|
||||
state.dragging_state = None;
|
||||
|
|
@ -1785,54 +1816,32 @@ where
|
|||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DataSource(
|
||||
wayland::DataSourceEvent::DndActionAccepted(action),
|
||||
))) => {
|
||||
let state = state();
|
||||
if let Some(DraggingState::Dnd(_, text)) = state.dragging_state.as_ref() {
|
||||
state.dragging_state = Some(DraggingState::Dnd(action, text.clone()));
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
#[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;
|
||||
};
|
||||
|
||||
Event::Dnd(DndEvent::Offer(
|
||||
rectangle,
|
||||
OfferEvent::Enter {
|
||||
x,
|
||||
y,
|
||||
mime_types,
|
||||
surface,
|
||||
},
|
||||
)) if rectangle == Some(dnd_id) => {
|
||||
let state = state();
|
||||
let is_clicked = text_layout.bounds().contains(Point {
|
||||
x: x 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;
|
||||
for m in &mime_types {
|
||||
if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
|
||||
let clone = m.clone();
|
||||
accepted = true;
|
||||
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),
|
||||
}
|
||||
})));
|
||||
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
|
||||
let position = if target > 0.0 {
|
||||
update_cache(state, value);
|
||||
|
|
@ -1846,57 +1855,11 @@ where
|
|||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
|
||||
wayland::DndOfferEvent::Motion { x, y },
|
||||
))) => {
|
||||
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
|
||||
return event::Status::Ignored;
|
||||
};
|
||||
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
|
||||
if rectangle == Some(dnd_id) =>
|
||||
{
|
||||
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;
|
||||
// existing logic for setting the selection
|
||||
let position = if target > 0.0 {
|
||||
|
|
@ -1910,13 +1873,7 @@ where
|
|||
return event::Status::Captured;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
|
||||
wayland::DndOfferEvent::DropPerformed,
|
||||
))) => {
|
||||
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
|
||||
return event::Status::Ignored;
|
||||
};
|
||||
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
|
||||
let state = state();
|
||||
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
|
||||
let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
|
||||
|
|
@ -1927,21 +1884,15 @@ where
|
|||
return event::Status::Captured;
|
||||
};
|
||||
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;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
|
||||
wayland::DndOfferEvent::Leave,
|
||||
))) => {
|
||||
Event::Dnd(DndEvent::Offer(
|
||||
rectangle,
|
||||
OfferEvent::Leave | OfferEvent::LeaveDestination,
|
||||
)) if rectangle == Some(dnd_id) => {
|
||||
let state = state();
|
||||
// ASHLEY TODO we should be able to reset but for now we don't if we are handling a
|
||||
// drop
|
||||
|
|
@ -1954,13 +1905,9 @@ where
|
|||
return event::Status::Captured;
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::DndOffer(
|
||||
wayland::DndOfferEvent::DndData { mime_type, data },
|
||||
))) => {
|
||||
let Some(on_dnd_command_produced) = on_dnd_command_produced else {
|
||||
return event::Status::Ignored;
|
||||
};
|
||||
|
||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
|
||||
if rectangle == Some(dnd_id) =>
|
||||
{
|
||||
let state = state();
|
||||
if let DndOfferState::Dropped = state.dnd_offer.clone() {
|
||||
state.dnd_offer = DndOfferState::None;
|
||||
|
|
@ -1982,9 +1929,6 @@ where
|
|||
shell.publish(message);
|
||||
}
|
||||
|
||||
shell.publish(on_dnd_command_produced(Box::new(move || {
|
||||
platform_specific::wayland::data_device::ActionInner::DndFinished
|
||||
})));
|
||||
let value = if is_secure {
|
||||
unsecured_value.secure()
|
||||
} else {
|
||||
|
|
@ -1995,26 +1939,6 @@ where
|
|||
}
|
||||
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,
|
||||
theme: &crate::Theme,
|
||||
layout: Layout<'_>,
|
||||
text_layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
tree: &Tree,
|
||||
value: &Value,
|
||||
|
|
@ -2083,7 +2008,7 @@ pub fn draw<'a, Message>(
|
|||
|
||||
let mut children_layout = layout.children();
|
||||
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);
|
||||
|
||||
|
|
@ -2172,7 +2097,7 @@ pub fn draw<'a, Message>(
|
|||
if let (Some(label_layout), Some(label)) = (label_layout, label) {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: label,
|
||||
content: label.to_string(),
|
||||
size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
|
||||
font: font.unwrap_or_else(|| renderer.default_font()),
|
||||
bounds: label_layout.bounds().size(),
|
||||
|
|
@ -2180,7 +2105,7 @@ pub fn draw<'a, Message>(
|
|||
vertical_alignment: alignment::Vertical::Top,
|
||||
line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
label_layout.bounds().position(),
|
||||
appearance.label_color,
|
||||
|
|
@ -2214,14 +2139,24 @@ pub fn draw<'a, Message>(
|
|||
let size = size.unwrap_or_else(|| renderer.default_size().0);
|
||||
|
||||
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) {
|
||||
cursor::State::Index(position) => {
|
||||
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 =
|
||||
((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2
|
||||
let is_cursor_visible = handling_dnd_offer
|
||||
|| ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
|
||||
% 2
|
||||
== 0;
|
||||
|
||||
if is_cursor_visible {
|
||||
|
|
@ -2263,10 +2198,10 @@ pub fn draw<'a, Message>(
|
|||
|
||||
let value_paragraph = &state.value;
|
||||
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) =
|
||||
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;
|
||||
|
||||
|
|
@ -2331,7 +2266,11 @@ pub fn draw<'a, Message>(
|
|||
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: if text.is_empty() { placeholder } else { &text },
|
||||
content: if text.is_empty() {
|
||||
placeholder.to_string()
|
||||
} else {
|
||||
text.clone()
|
||||
},
|
||||
font,
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(size),
|
||||
|
|
@ -2339,7 +2278,7 @@ pub fn draw<'a, Message>(
|
|||
vertical_alignment: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
bounds.position(),
|
||||
color,
|
||||
|
|
@ -2374,7 +2313,7 @@ pub fn draw<'a, Message>(
|
|||
if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: helper_text,
|
||||
content: helper_text.to_string(), // TODO remove to_string?
|
||||
size: iced::Pixels(helper_text_size),
|
||||
font,
|
||||
bounds: helper_text_layout.bounds().size(),
|
||||
|
|
@ -2382,7 +2321,7 @@ pub fn draw<'a, Message>(
|
|||
vertical_alignment: alignment::Vertical::Top,
|
||||
line_height: helper_line_height,
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrap: text::Wrap::default(),
|
||||
wrapping: text::Wrapping::default(),
|
||||
},
|
||||
helper_text_layout.bounds().position(),
|
||||
text_color,
|
||||
|
|
@ -2414,11 +2353,23 @@ pub fn mouse_interaction(
|
|||
pub struct TextInputString(pub String);
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
impl DataFromMimeType for TextInputString {
|
||||
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
|
||||
SUPPORTED_TEXT_MIME_TYPES
|
||||
.contains(&mime_type)
|
||||
.then(|| self.0.as_bytes().to_vec())
|
||||
impl AsMimeTypes for TextInputString {
|
||||
fn available(&self) -> Cow<'static, [String]> {
|
||||
Cow::Owned(
|
||||
SUPPORTED_TEXT_MIME_TYPES
|
||||
.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 {
|
||||
#[default]
|
||||
None,
|
||||
OutsideWidget(Vec<String>, DndAction),
|
||||
HandlingOffer(Vec<String>, DndAction),
|
||||
Dropped,
|
||||
}
|
||||
|
|
@ -2446,17 +2396,16 @@ pub(crate) struct DndOfferState;
|
|||
#[derive(Debug, Default, Clone)]
|
||||
#[must_use]
|
||||
pub struct State {
|
||||
pub value: crate::Paragraph,
|
||||
pub placeholder: crate::Paragraph,
|
||||
pub label: crate::Paragraph,
|
||||
pub helper_text: crate::Paragraph,
|
||||
pub value: crate::Plain,
|
||||
pub placeholder: crate::Plain,
|
||||
pub label: crate::Plain,
|
||||
pub helper_text: crate::Plain,
|
||||
pub dirty: bool,
|
||||
pub is_secure: bool,
|
||||
pub is_read_only: bool,
|
||||
select_on_focus: bool,
|
||||
is_focused: Option<Focus>,
|
||||
dragging_state: Option<DraggingState>,
|
||||
#[cfg(feature = "wayland")]
|
||||
dnd_offer: DndOfferState,
|
||||
is_pasting: Option<Value>,
|
||||
last_click: Option<mouse::Click>,
|
||||
|
|
@ -2522,15 +2471,14 @@ impl State {
|
|||
pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
|
||||
Self {
|
||||
is_secure,
|
||||
value: crate::Paragraph::new(),
|
||||
placeholder: crate::Paragraph::new(),
|
||||
label: crate::Paragraph::new(),
|
||||
helper_text: crate::Paragraph::new(),
|
||||
value: crate::Plain::default(),
|
||||
placeholder: crate::Plain::default(),
|
||||
label: crate::Plain::default(),
|
||||
helper_text: crate::Plain::default(),
|
||||
is_read_only,
|
||||
is_focused: None,
|
||||
select_on_focus: false,
|
||||
dragging_state: None,
|
||||
#[cfg(feature = "wayland")]
|
||||
dnd_offer: DndOfferState::default(),
|
||||
is_pasting: None,
|
||||
last_click: None,
|
||||
|
|
@ -2654,6 +2602,7 @@ fn find_cursor_position(
|
|||
|
||||
let char_offset = state
|
||||
.value
|
||||
.raw()
|
||||
.hit_test(Point::new(x + offset, text_bounds.height / 2.0))
|
||||
.map(text::Hit::cursor)?;
|
||||
|
||||
|
|
@ -2677,7 +2626,7 @@ fn replace_paragraph(
|
|||
let mut children_layout = layout.children();
|
||||
let text_bounds = children_layout.next().unwrap().bounds();
|
||||
|
||||
state.value = crate::Paragraph::with_text(Text {
|
||||
state.value = crate::Plain::new(Text {
|
||||
font,
|
||||
line_height,
|
||||
content: &value.to_string(),
|
||||
|
|
@ -2686,7 +2635,7 @@ fn replace_paragraph(
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
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) =
|
||||
measure_cursor_and_scroll_offset(&state.value, text_bounds, focus_position);
|
||||
measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);
|
||||
|
||||
offset
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::rc::Rc;
|
|||
|
||||
use crate::widget::container;
|
||||
use crate::widget::Column;
|
||||
use crate::Command;
|
||||
use iced::{Padding, Task};
|
||||
use iced_core::Element;
|
||||
use slotmap::new_key_type;
|
||||
use slotmap::SlotMap;
|
||||
|
|
@ -47,15 +47,15 @@ pub fn toaster<'a, Message: Clone + 'static>(
|
|||
button::icon(icon::from_name("window-close-symbolic"))
|
||||
.on_press((toasts.on_close)(id)),
|
||||
)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.spacing(space_xxs),
|
||||
)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.spacing(space_s);
|
||||
|
||||
container(row)
|
||||
.padding([space_xxs, space_s, space_xxs, space_m])
|
||||
.style(crate::style::Container::Tooltip)
|
||||
.class(crate::style::Container::Tooltip)
|
||||
};
|
||||
|
||||
let col = toasts
|
||||
|
|
@ -175,7 +175,7 @@ impl<Message: Clone + Send + 'static> Toasts<Message> {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
self.toasts.remove(
|
||||
self.queue
|
||||
|
|
@ -200,7 +200,7 @@ impl<Message: Clone + Send + 'static> Toasts<Message> {
|
|||
}
|
||||
#[cfg(not(feature = "tokio"))]
|
||||
{
|
||||
Command::none()
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ use iced_core::widget::Operation;
|
|||
use iced_core::Element;
|
||||
use iced_core::Overlay;
|
||||
use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget};
|
||||
use iced_renderer::core::widget::OperationOutputWrapper;
|
||||
|
||||
pub struct Toaster<'a, Message, Theme, Renderer> {
|
||||
toasts: Element<'a, Message, Theme, Renderer>,
|
||||
|
|
@ -90,7 +89,7 @@ where
|
|||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
|
|
@ -142,22 +141,23 @@ where
|
|||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
//TODO: this hides the overlay of the content during the toast
|
||||
if self.is_empty {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut state.children[0], layout, renderer)
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut state.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
} else {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
Some(overlay::Element::new(
|
||||
bounds.position(),
|
||||
Box::new(ToasterOverlay::new(
|
||||
&mut state.children[1],
|
||||
&mut self.toasts,
|
||||
)),
|
||||
))
|
||||
Some(overlay::Element::new(Box::new(ToasterOverlay::new(
|
||||
&mut state.children[1],
|
||||
&mut self.toasts,
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ where
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.content.as_widget().drag_destinations(
|
||||
&state.children[0],
|
||||
|
|
@ -196,13 +196,7 @@ impl<'a, 'b, Message, Theme, Renderer> Overlay<Message, Theme, Renderer>
|
|||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_translation: Vector,
|
||||
) -> Node {
|
||||
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
|
||||
let limits = Limits::new(Size::ZERO, bounds);
|
||||
|
||||
let mut node = self
|
||||
|
|
@ -276,7 +270,7 @@ where
|
|||
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
|
||||
self.element
|
||||
.as_widget_mut()
|
||||
.overlay(self.state, layout, renderer)
|
||||
.overlay(self.state, layout, renderer, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@
|
|||
use iced::{widget, Length};
|
||||
use iced_core::text;
|
||||
|
||||
pub fn toggler<'a, Message, Theme: iced_widget::toggler::StyleSheet, Renderer>(
|
||||
label: impl Into<Option<String>>,
|
||||
pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>(
|
||||
is_checked: bool,
|
||||
f: impl Fn(bool) -> Message + 'a,
|
||||
) -> widget::Toggler<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer + text::Renderer,
|
||||
{
|
||||
widget::Toggler::new(label, is_checked, f)
|
||||
widget::Toggler::new(is_checked)
|
||||
.on_toggle(f)
|
||||
.size(24)
|
||||
.width(Length::Shrink)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ impl<'a, Message: 'static + Clone> Warning<'a, Message> {
|
|||
widget::row::with_capacity(2)
|
||||
.push(label)
|
||||
.push(close_button)
|
||||
.align_items(Alignment::Center)
|
||||
.align_y(Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.style(theme::Container::custom(warning_container))
|
||||
.class(theme::Container::custom(warning_container))
|
||||
.padding(10)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -57,9 +57,9 @@ impl<'a, Message: 'static + Clone> From<Warning<'a, Message>> for Element<'a, Me
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn warning_container(theme: &Theme) -> widget::container::Appearance {
|
||||
pub fn warning_container(theme: &Theme) -> widget::container::Style {
|
||||
let cosmic = theme.cosmic();
|
||||
widget::container::Appearance {
|
||||
widget::container::Style {
|
||||
icon_color: Some(theme.cosmic().warning.on.into()),
|
||||
text_color: Some(theme.cosmic().warning.on.into()),
|
||||
background: Some(Background::Color(theme.cosmic().warning_color().into())),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue