Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95756b1a57 | ||
|
|
c423ad1bfc | ||
|
|
8d7bcab258 | ||
|
|
c162a1f24a | ||
|
|
3f9e93067b | ||
|
|
917af9fda2 | ||
|
|
9b465a8b5c | ||
|
|
9cac422c24 | ||
|
|
0fc4638af3 | ||
|
|
3d8d8915be | ||
|
|
46d9f0c344 | ||
|
|
0d69cd9183 | ||
|
|
52116d2f36 | ||
|
|
0e72508dcc | ||
|
|
1d7113a244 | ||
|
|
e287a789c1 | ||
|
|
6caccaba33 |
26 changed files with 643 additions and 325 deletions
10
Cargo.toml
10
Cargo.toml
|
|
@ -58,6 +58,7 @@ desktop = [
|
||||||
"process",
|
"process",
|
||||||
"dep:cosmic-settings-config",
|
"dep:cosmic-settings-config",
|
||||||
"dep:freedesktop-desktop-entry",
|
"dep:freedesktop-desktop-entry",
|
||||||
|
"dep:image-extras",
|
||||||
"dep:mime",
|
"dep:mime",
|
||||||
"dep:shlex",
|
"dep:shlex",
|
||||||
"tokio?/io-util",
|
"tokio?/io-util",
|
||||||
|
|
@ -141,9 +142,14 @@ css-color = "0.2.8"
|
||||||
derive_setters = "0.1.9"
|
derive_setters = "0.1.9"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
image = { version = "0.25.10", default-features = false, features = [
|
image = { version = "0.25.10", default-features = false, features = [
|
||||||
|
"ico",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
] }
|
] }
|
||||||
|
image-extras = { version = "0.1.0", default-features = false, features = [
|
||||||
|
"xpm",
|
||||||
|
"xbm",
|
||||||
|
], optional = true }
|
||||||
libc = { version = "0.2.183", optional = true }
|
libc = { version = "0.2.183", optional = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = { version = "0.3.17", optional = true }
|
mime = { version = "0.3.17", optional = true }
|
||||||
|
|
@ -170,12 +176,12 @@ cosmic-config = { path = "cosmic-config", features = ["dbus"] }
|
||||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||||
zbus = { version = "5.14.0", default-features = false }
|
zbus = { version = "5.14.0", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||||
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
freedesktop-icons = { package = "cosmic-freedesktop-icons", git = "https://github.com/pop-os/freedesktop-icons" }
|
||||||
freedesktop-desktop-entry = { version = "0.8.1", optional = true }
|
freedesktop-desktop-entry = { version = "0.8.1", optional = true }
|
||||||
shlex = { version = "1.3.0", optional = true }
|
shlex = { version = "1.3.0", optional = true }
|
||||||
|
|
||||||
[target.'cfg(not(unix))'.dependencies]
|
[target.'cfg(any(not(unix), target_os = "macos"))'.dependencies]
|
||||||
# Used to embed bundled icons for non-unix platforms.
|
# Used to embed bundled icons for non-unix platforms.
|
||||||
phf = { version = "0.13.1", features = ["macros"] }
|
phf = { version = "0.13.1", features = ["macros"] }
|
||||||
|
|
||||||
|
|
|
||||||
4
build.rs
4
build.rs
|
|
@ -3,7 +3,9 @@ use std::env;
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
|
|
||||||
if env::var_os("CARGO_CFG_UNIX").is_none() {
|
if env::var_os("CARGO_CFG_UNIX").is_none()
|
||||||
|
|| env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos")
|
||||||
|
{
|
||||||
generate_bundled_icons();
|
generate_bundled_icons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,5 @@ features = [
|
||||||
"single-instance",
|
"single-instance",
|
||||||
"surface-message",
|
"surface-message",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ impl cosmic::Application for App {
|
||||||
.map_or("No page selected", String::as_str);
|
.map_or("No page selected", String::as_str);
|
||||||
|
|
||||||
let centered = widget::container(
|
let centered = widget::container(
|
||||||
widget::column::with_capacity(5)
|
widget::column::with_capacity(14)
|
||||||
.push(widget::text::body(page_content))
|
.push(widget::text::body(page_content))
|
||||||
.push(
|
.push(
|
||||||
widget::text_input::text_input("", &self.input_1)
|
widget::text_input::text_input("", &self.input_1)
|
||||||
|
|
@ -223,6 +223,7 @@ impl cosmic::Application for App {
|
||||||
.on_clear(Message::Ignore),
|
.on_clear(Message::Ignore),
|
||||||
)
|
)
|
||||||
.push(widget::progress_bar::circular::Circular::new().size(50.0))
|
.push(widget::progress_bar::circular::Circular::new().size(50.0))
|
||||||
|
.push(widget::progress_bar::circular::Circular::new().size(20.0))
|
||||||
.push(
|
.push(
|
||||||
widget::progress_bar::linear::Linear::new()
|
widget::progress_bar::linear::Linear::new()
|
||||||
.girth(10.0)
|
.girth(10.0)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ links = Links
|
||||||
developers = Entwickler(innen)
|
developers = Entwickler(innen)
|
||||||
designers = Designer(innen)
|
designers = Designer(innen)
|
||||||
artists = Künstler(innen)
|
artists = Künstler(innen)
|
||||||
translators = Übersetzer*innen
|
translators = Übersetzer(innen)
|
||||||
documenters = Dokumentierer(innen)
|
documenters = Dokumentierer(innen)
|
||||||
# Calendar
|
# Calendar
|
||||||
january = Januar { $year }
|
january = Januar { $year }
|
||||||
|
|
|
||||||
0
i18n/eu/libcosmic.ftl
Normal file
0
i18n/eu/libcosmic.ftl
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
close = Mdel
|
||||||
|
license = Turagt
|
||||||
|
links = Iseɣwan
|
||||||
|
developers = Ineflayen
|
||||||
|
artists = Inaẓuren
|
||||||
|
translators = Imsuqlen
|
||||||
|
january = Yennayer { $year }
|
||||||
|
february = Fuṛar { $year }
|
||||||
|
march = Meɣres { $year }
|
||||||
|
april = Yebrir { $year }
|
||||||
|
may = Mayyu { $year }
|
||||||
|
june = Yunyu { $year }
|
||||||
|
july = Yulyu { $year }
|
||||||
|
august = Ɣuct { $year }
|
||||||
|
september = Ctembeṛ { $year }
|
||||||
|
october = Tubeṛ { $year }
|
||||||
|
november = Wambeṛ { $year }
|
||||||
|
december = Dujembeṛ { $year }
|
||||||
|
documenters = Imeskaren
|
||||||
|
monday = Arim
|
||||||
|
mon = Ari
|
||||||
|
tuesday = Aram
|
||||||
|
tue = Ara
|
||||||
|
wednesday = Ahad
|
||||||
|
wed = Aha
|
||||||
|
thursday = Amhad
|
||||||
|
thu = Amh
|
||||||
|
friday = Sem
|
||||||
|
fri = Sm
|
||||||
|
saturday = Sed
|
||||||
|
sat = Sd
|
||||||
|
sunday = Acer
|
||||||
|
sun = Ace
|
||||||
|
|
@ -2,26 +2,33 @@ february = { $year }년 2월
|
||||||
close = 닫기
|
close = 닫기
|
||||||
documenters = 문서 작성자
|
documenters = 문서 작성자
|
||||||
november = { $year }년 11월
|
november = { $year }년 11월
|
||||||
friday = 금
|
friday = 금요일
|
||||||
tuesday = 화
|
tuesday = 화요일
|
||||||
may = { $year }년 5월
|
may = { $year }년 5월
|
||||||
wednesday = 수
|
wednesday = 수요일
|
||||||
april = { $year }년 4월
|
april = { $year }년 4월
|
||||||
monday = 월
|
monday = 월요일
|
||||||
translators = 번역가
|
translators = 번역가
|
||||||
artists = 아티스트
|
artists = 아티스트
|
||||||
license = 라이선스
|
license = 라이선스
|
||||||
december = { $year }년 12월
|
december = { $year }년 12월
|
||||||
sunday = 일
|
sunday = 일요일
|
||||||
links = 링크
|
links = 링크
|
||||||
march = { $year }년 3월
|
march = { $year }년 3월
|
||||||
june = { $year }년 6월
|
june = { $year }년 6월
|
||||||
saturday = 토
|
saturday = 토요일
|
||||||
august = { $year }년 8월
|
august = { $year }년 8월
|
||||||
developers = 개발자
|
developers = 개발자
|
||||||
july = { $year }년 7월
|
july = { $year }년 7월
|
||||||
thursday = 목
|
thursday = 목요일
|
||||||
september = { $year }년 9월
|
september = { $year }년 9월
|
||||||
designers = 디자이너
|
designers = 디자이너
|
||||||
october = { $year }년 10월
|
october = { $year }년 10월
|
||||||
january = { $year }년 1월
|
january = { $year }년 1월
|
||||||
|
mon = 월
|
||||||
|
tue = 화
|
||||||
|
wed = 수
|
||||||
|
thu = 목
|
||||||
|
fri = 금
|
||||||
|
sat = 토
|
||||||
|
sun = 일
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
close = 關閉
|
||||||
|
developers = 開發人員
|
||||||
|
designers = 設計人員
|
||||||
|
artists = 美編設計
|
||||||
|
translators = 翻譯人員
|
||||||
|
documenters = 文件編輯人員
|
||||||
|
january = { $year } 年 1 月
|
||||||
|
monday = 星期一
|
||||||
|
tuesday = 星期二
|
||||||
|
wednesday = 星期三
|
||||||
|
thursday = 星期四
|
||||||
|
friday = 星期五
|
||||||
|
saturday = 星期六
|
||||||
|
sunday = 星期日
|
||||||
|
mon = 週一
|
||||||
|
tue = 週二
|
||||||
|
wed = 週三
|
||||||
|
thu = 週四
|
||||||
|
fri = 週五
|
||||||
|
sat = 週六
|
||||||
|
sun = 週日
|
||||||
|
license = 授權
|
||||||
|
links = 連結
|
||||||
|
february = { $year } 年 2 月
|
||||||
|
march = { $year } 年 3 月
|
||||||
|
april = { $year } 年 4 月
|
||||||
|
may = { $year } 年 5 月
|
||||||
|
june = { $year } 年 6 月
|
||||||
|
july = { $year } 年 7 月
|
||||||
|
august = { $year } 年 8 月
|
||||||
|
september = { $year } 年 9 月
|
||||||
|
october = { $year } 年 10 月
|
||||||
|
november = { $year } 年 11 月
|
||||||
|
december = { $year } 年 12 月
|
||||||
2
iced
2
iced
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7fd263d99e6ae1b07e51f25bda3367f7463806b1
|
Subproject commit 78caabba7ef91cd1030da6f70b41d266704ffece
|
||||||
|
|
@ -128,6 +128,9 @@ impl<A: crate::app::Application> BootFn<cosmic::Cosmic<A>, crate::Action<A::Mess
|
||||||
///
|
///
|
||||||
/// Returns error on application failure.
|
/// Returns error on application failure.
|
||||||
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
image_extras::register();
|
||||||
|
|
||||||
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
|
#[cfg(all(target_env = "gnu", not(target_os = "windows")))]
|
||||||
if let Some(threshold) = settings.default_mmap_threshold {
|
if let Some(threshold) = settings.default_mmap_threshold {
|
||||||
crate::malloc::limit_mmap_threshold(threshold);
|
crate::malloc::limit_mmap_threshold(threshold);
|
||||||
|
|
@ -194,6 +197,9 @@ where
|
||||||
App::Flags: CosmicFlags,
|
App::Flags: CosmicFlags,
|
||||||
App::Message: Clone + std::fmt::Debug + Send + 'static,
|
App::Message: Clone + std::fmt::Debug + Send + 'static,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
image_extras::register();
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ impl DefaultStyle for Theme {
|
||||||
fn default_style(&self) -> Appearance {
|
fn default_style(&self) -> Appearance {
|
||||||
let cosmic = self.cosmic();
|
let cosmic = self.cosmic();
|
||||||
Appearance {
|
Appearance {
|
||||||
icon_color: cosmic.bg_color().into(),
|
icon_color: cosmic.on_bg_color().into(),
|
||||||
background_color: cosmic.bg_color().into(),
|
background_color: cosmic.bg_color().into(),
|
||||||
text_color: cosmic.on_bg_color().into(),
|
text_color: cosmic.on_bg_color().into(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub enum Button {
|
||||||
IconVertical,
|
IconVertical,
|
||||||
Image,
|
Image,
|
||||||
Link,
|
Link,
|
||||||
ListItem,
|
ListItem([f32; 4]),
|
||||||
MenuFolder,
|
MenuFolder,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuRoot,
|
MenuRoot,
|
||||||
|
|
@ -148,8 +148,8 @@ pub fn appearance(
|
||||||
appearance.text_color = Some(component.on.into());
|
appearance.text_color = Some(component.on.into());
|
||||||
corner_radii = &cosmic.corner_radii.radius_s;
|
corner_radii = &cosmic.corner_radii.radius_s;
|
||||||
}
|
}
|
||||||
Button::ListItem => {
|
Button::ListItem(radii) => {
|
||||||
corner_radii = &[0.0; 4];
|
corner_radii = radii;
|
||||||
let (background, text, icon) = color(&cosmic.background.component);
|
let (background, text, icon) = color(&cosmic.background.component);
|
||||||
|
|
||||||
if selected {
|
if selected {
|
||||||
|
|
@ -197,7 +197,7 @@ impl Catalog for crate::Theme {
|
||||||
return active(focused, self);
|
return active(focused, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
appearance(self, focused, selected, false, style, move |component| {
|
let mut s = appearance(self, focused, selected, false, style, move |component| {
|
||||||
let text_color = if matches!(
|
let text_color = if matches!(
|
||||||
style,
|
style,
|
||||||
Button::Icon | Button::IconVertical | Button::HeaderBar
|
Button::Icon | Button::IconVertical | Button::HeaderBar
|
||||||
|
|
@ -209,7 +209,15 @@ impl Catalog for crate::Theme {
|
||||||
};
|
};
|
||||||
|
|
||||||
(component.base.into(), text_color, text_color)
|
(component.base.into(), text_color, text_color)
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if let Button::ListItem(_) = style {
|
||||||
|
if !selected {
|
||||||
|
s.background = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disabled(&self, style: &Self::Class) -> Style {
|
fn disabled(&self, style: &Self::Class) -> Style {
|
||||||
|
|
@ -237,7 +245,7 @@ impl Catalog for crate::Theme {
|
||||||
return hovered(focused, self);
|
return hovered(focused, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
appearance(
|
let mut s = appearance(
|
||||||
self,
|
self,
|
||||||
focused || matches!(style, Button::Image),
|
focused || matches!(style, Button::Image),
|
||||||
selected,
|
selected,
|
||||||
|
|
@ -256,7 +264,15 @@ impl Catalog for crate::Theme {
|
||||||
|
|
||||||
(component.hover.into(), text_color, text_color)
|
(component.hover.into(), text_color, text_color)
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
|
if let Button::ListItem(_) = style {
|
||||||
|
if !selected {
|
||||||
|
s.background = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
|
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub mod application {
|
||||||
iced::theme::Style {
|
iced::theme::Style {
|
||||||
background_color: cosmic.bg_color().into(),
|
background_color: cosmic.bg_color().into(),
|
||||||
text_color: cosmic.on_bg_color().into(),
|
text_color: cosmic.on_bg_color().into(),
|
||||||
icon_color: cosmic.bg_color().into(),
|
icon_color: cosmic.on_bg_color().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Apply, Element, fl,
|
Apply, Element, fl,
|
||||||
iced::{Alignment, Length},
|
iced::{Alignment, Length},
|
||||||
widget::{self, space},
|
widget::{self, list},
|
||||||
};
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
#[derive(Debug, Default, Clone, derive_setters::Setters)]
|
||||||
#[setters(into, strip_option)]
|
#[setters(into, strip_option)]
|
||||||
|
|
@ -104,19 +105,23 @@ pub fn about<'a, Message: Clone + 'static>(
|
||||||
space_xxs, space_m, ..
|
space_xxs, space_m, ..
|
||||||
} = crate::theme::spacing();
|
} = crate::theme::spacing();
|
||||||
|
|
||||||
let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> {
|
let svg_accent = Rc::new(|theme: &crate::Theme| widget::svg::Style {
|
||||||
widget::row::with_capacity(3)
|
color: Some(theme.cosmic().accent_text_color().into()),
|
||||||
.push(widget::text(name))
|
});
|
||||||
.push(space::horizontal())
|
|
||||||
|
let section_button = |name: &'a str, url: &'a str| -> list::ListButton<'a, Message> {
|
||||||
|
widget::row::with_capacity(2)
|
||||||
|
.push(widget::text::body(name).width(Length::Fill))
|
||||||
.push_maybe(
|
.push_maybe(
|
||||||
(!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()),
|
(!url.is_empty()).then_some(
|
||||||
|
widget::icon::from_name("link-symbolic")
|
||||||
|
.icon()
|
||||||
|
.class(crate::theme::Svg::Custom(svg_accent.clone())),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
.apply(widget::button::custom)
|
.apply(list::button)
|
||||||
.class(crate::theme::Button::Link)
|
|
||||||
.on_press(on_url_press(url))
|
.on_press(on_url_press(url))
|
||||||
.width(Length::Fill)
|
|
||||||
.into()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let section = |list: &'a Vec<(String, String)>, title: String| {
|
let section = |list: &'a Vec<(String, String)>, title: String| {
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ use iced_core::image::Renderer as ImageRenderer;
|
||||||
use iced_core::mouse::Cursor;
|
use iced_core::mouse::Cursor;
|
||||||
use iced_core::widget::{Tree, tree};
|
use iced_core::widget::{Tree, tree};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
|
Clipboard, ContentFit, Element, Event, Layout, Length, Rectangle, Rotation, Shell, Size,
|
||||||
event, layout, renderer, window,
|
Widget, event, layout, renderer, window,
|
||||||
};
|
};
|
||||||
use iced_widget::image::{self, Handle};
|
use iced_widget::image::{self, FilterMethod, Handle};
|
||||||
use image_rs::AnimationDecoder;
|
use image_rs::AnimationDecoder;
|
||||||
use image_rs::codecs::gif::GifDecoder;
|
use image_rs::codecs::gif::GifDecoder;
|
||||||
use image_rs::codecs::png::PngDecoder;
|
use image_rs::codecs::png::PngDecoder;
|
||||||
|
|
@ -146,7 +146,7 @@ impl Frames {
|
||||||
|
|
||||||
match image_type {
|
match image_type {
|
||||||
ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?),
|
ImageType::Gif => Self::from_decoder(GifDecoder::new(io::Cursor::new(bytes))?),
|
||||||
ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()),
|
ImageType::Apng => Self::from_decoder(PngDecoder::new(io::Cursor::new(bytes))?.apng()?),
|
||||||
ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?),
|
ImageType::WebP => Self::from_decoder(WebPDecoder::new(io::Cursor::new(bytes))?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,10 +168,10 @@ impl Frames {
|
||||||
let first = frames.first().cloned().unwrap();
|
let first = frames.first().cloned().unwrap();
|
||||||
let total_bytes = frames
|
let total_bytes = frames
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| match f.handle.data() {
|
.map(|f| match &f.handle {
|
||||||
iced_core::image::Handle::Path(..) => 0,
|
Handle::Path(..) => 0,
|
||||||
iced_core::image::Handle::Bytes(_, b) => b.len(),
|
Handle::Bytes(_, b) => b.len(),
|
||||||
iced_core::image::Handle::Rgba { pixels, .. } => pixels.len(),
|
Handle::Rgba { pixels, .. } => pixels.len(),
|
||||||
})
|
})
|
||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
.try_into()
|
.try_into()
|
||||||
|
|
@ -324,7 +324,11 @@ where
|
||||||
&self.frames.first.handle,
|
&self.frames.first.handle,
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
|
None,
|
||||||
self.content_fit,
|
self.content_fit,
|
||||||
|
Rotation::default(),
|
||||||
|
false,
|
||||||
|
[0.0; 4],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,37 +375,18 @@ where
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
// Pulled from iced_native::widget::<Image as Widget>::draw
|
iced_widget::image::draw(
|
||||||
//
|
renderer,
|
||||||
// TODO: export iced_native::widget::image::draw as standalone function
|
layout,
|
||||||
{
|
&state.current.frame.handle,
|
||||||
let Size { width, height } = renderer.dimensions(&state.current.frame.handle);
|
None,
|
||||||
let image_size = Size::new(width as f32, height as f32);
|
iced_core::border::Radius::default(),
|
||||||
|
self.content_fit,
|
||||||
let bounds = layout.bounds();
|
FilterMethod::default(),
|
||||||
let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
|
Rotation::default(),
|
||||||
|
1.0,
|
||||||
let render = |renderer: &mut Renderer| {
|
1.0,
|
||||||
let offset = Vector::new(
|
|
||||||
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
|
|
||||||
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let drawing_bounds = Rectangle {
|
|
||||||
width: adjusted_fit.width,
|
|
||||||
height: adjusted_fit.height,
|
|
||||||
..bounds
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.draw(state.current.frame.handle.clone(), drawing_bounds + offset);
|
|
||||||
};
|
|
||||||
|
|
||||||
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height {
|
|
||||||
renderer.with_layer(bounds, render);
|
|
||||||
} else {
|
|
||||||
render(renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
//! Embedded icons for platforms which do not support icon themes yet.
|
//! Embedded icons for platforms which do not support icon themes yet.
|
||||||
|
|
||||||
/// Icon bundling is not enabled on unix platforms.
|
/// Icon bundling is not enabled on unix platforms.
|
||||||
#[cfg(unix)]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
pub fn get(icon_name: &str) -> Option<super::Data> {
|
pub fn get(icon_name: &str) -> Option<super::Data> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(any(not(unix), target_os = "macos"))]
|
||||||
/// Get a bundled icon on non-unix platforms.
|
/// Get a bundled icon on non-unix platforms.
|
||||||
pub fn get(icon_name: &str) -> Option<super::Data> {
|
pub fn get(icon_name: &str) -> Option<super::Data> {
|
||||||
ICONS
|
ICONS
|
||||||
|
|
@ -17,5 +17,5 @@ pub fn get(icon_name: &str) -> Option<super::Data> {
|
||||||
.map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes)))
|
.map(|bytes| super::Data::Svg(crate::iced::widget::svg::Handle::from_memory(*bytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(any(not(unix), target_os = "macos"))]
|
||||||
include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bundled_icons.rs"));
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ impl Named {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn path(self) -> Option<PathBuf> {
|
pub fn path(self) -> Option<PathBuf> {
|
||||||
let name = &*self.name;
|
let name = &*self.name;
|
||||||
|
|
@ -107,7 +107,7 @@ impl Named {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(any(not(unix), target_os = "macos"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn path(self) -> Option<PathBuf> {
|
pub fn path(self) -> Option<PathBuf> {
|
||||||
//TODO: implement icon lookup for Windows
|
//TODO: implement icon lookup for Windows
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
use iced_core::Padding;
|
|
||||||
use iced_widget::container::Catalog;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
Apply, Element, theme,
|
|
||||||
widget::{container, divider, space::vertical},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
|
||||||
ListColumn::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub struct ListColumn<'a, Message> {
|
|
||||||
spacing: u16,
|
|
||||||
padding: Padding,
|
|
||||||
list_item_padding: Padding,
|
|
||||||
divider_padding: u16,
|
|
||||||
style: theme::Container<'a>,
|
|
||||||
children: Vec<Element<'a, Message>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Message: 'static> Default for ListColumn<'_, Message> {
|
|
||||||
fn default() -> Self {
|
|
||||||
let cosmic_theme::Spacing {
|
|
||||||
space_xxs, space_m, ..
|
|
||||||
} = theme::spacing();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
spacing: 0,
|
|
||||||
padding: Padding::from(0),
|
|
||||||
divider_padding: 16,
|
|
||||||
list_item_padding: [space_xxs, space_m].into(),
|
|
||||||
style: theme::Container::List,
|
|
||||||
children: Vec::with_capacity(4),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn add(self, item: impl Into<Element<'a, Message>>) -> Self {
|
|
||||||
#[inline(never)]
|
|
||||||
fn inner<'a, Message: 'static>(
|
|
||||||
mut this: ListColumn<'a, Message>,
|
|
||||||
item: Element<'a, Message>,
|
|
||||||
) -> ListColumn<'a, Message> {
|
|
||||||
if !this.children.is_empty() {
|
|
||||||
this.children.push(
|
|
||||||
container(divider::horizontal::default())
|
|
||||||
.padding([0, this.divider_padding])
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure a minimum height of 32.
|
|
||||||
let list_item = crate::widget::row![
|
|
||||||
container(item).align_y(iced::Alignment::Center),
|
|
||||||
vertical().height(iced::Length::Fixed(32.))
|
|
||||||
]
|
|
||||||
.padding(this.list_item_padding)
|
|
||||||
.align_y(iced::Alignment::Center);
|
|
||||||
|
|
||||||
this.children.push(list_item.into());
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
inner(self, item.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn spacing(mut self, spacing: u16) -> Self {
|
|
||||||
self.spacing = spacing;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the style variant of this [`Circular`].
|
|
||||||
#[inline]
|
|
||||||
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
|
|
||||||
self.style = style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
|
||||||
self.padding = padding.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn divider_padding(mut self, padding: u16) -> Self {
|
|
||||||
self.divider_padding = padding;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
|
||||||
self.list_item_padding = padding.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_element(self) -> Element<'a, Message> {
|
|
||||||
crate::widget::column::with_children(self.children)
|
|
||||||
.spacing(self.spacing)
|
|
||||||
.padding(self.padding)
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.apply(container)
|
|
||||||
.padding([self.spacing, 0])
|
|
||||||
.class(self.style)
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
|
||||||
fn from(column: ListColumn<'a, Message>) -> Self {
|
|
||||||
column.into_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
213
src/widget/list/list_column.rs
Normal file
213
src/widget/list/list_column.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::widget::container::Catalog;
|
||||||
|
use crate::widget::{button, column, container, divider, row, space::vertical};
|
||||||
|
use crate::{Apply, Element, theme};
|
||||||
|
use iced::{Length, Padding};
|
||||||
|
|
||||||
|
/// A button list item for use in a [`ListColumn`].
|
||||||
|
pub struct ListButton<'a, Message> {
|
||||||
|
content: Element<'a, Message>,
|
||||||
|
on_press: Option<Message>,
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`ListButton`] with the given content.
|
||||||
|
pub fn button<'a, Message>(content: impl Into<Element<'a, Message>>) -> ListButton<'a, Message> {
|
||||||
|
ListButton {
|
||||||
|
content: content.into(),
|
||||||
|
on_press: None,
|
||||||
|
selected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> ListButton<'a, Message> {
|
||||||
|
pub fn on_press(mut self, on_press: Message) -> Self {
|
||||||
|
self.on_press = Some(on_press);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
|
||||||
|
self.on_press = on_press;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ListItem<'a, Message> {
|
||||||
|
Element(Element<'a, Message>),
|
||||||
|
Button(ListButton<'a, Message>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for types that can be added to a [`ListColumn`].
|
||||||
|
pub trait IntoListItem<'a, Message> {
|
||||||
|
fn into_list_item(self) -> ListItem<'a, Message>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, T> IntoListItem<'a, Message> for T
|
||||||
|
where
|
||||||
|
T: Into<Element<'a, Message>>,
|
||||||
|
{
|
||||||
|
fn into_list_item(self) -> ListItem<'a, Message> {
|
||||||
|
ListItem::Element(self.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message> IntoListItem<'a, Message> for ListButton<'a, Message> {
|
||||||
|
fn into_list_item(self) -> ListItem<'a, Message> {
|
||||||
|
ListItem::Button(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshots the padding values at the moment an item is added
|
||||||
|
struct ListEntry<'a, Message> {
|
||||||
|
item: ListItem<'a, Message>,
|
||||||
|
item_padding: Padding,
|
||||||
|
divider_padding: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct ListColumn<'a, Message> {
|
||||||
|
list_item_padding: Padding,
|
||||||
|
divider_padding: u16,
|
||||||
|
style: theme::Container<'a>,
|
||||||
|
children: Vec<ListEntry<'a, Message>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
||||||
|
ListColumn::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_capacity<'a, Message: 'static>(capacity: usize) -> ListColumn<'a, Message> {
|
||||||
|
let cosmic_theme::Spacing {
|
||||||
|
space_xxs, space_m, ..
|
||||||
|
} = theme::spacing();
|
||||||
|
|
||||||
|
ListColumn {
|
||||||
|
list_item_padding: [space_xxs, space_m].into(),
|
||||||
|
divider_padding: 0,
|
||||||
|
style: theme::Container::List,
|
||||||
|
children: Vec::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message: 'static> Default for ListColumn<'_, Message> {
|
||||||
|
fn default() -> Self {
|
||||||
|
with_capacity(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> ListColumn<'a, Message> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a [`ListItem`] to the [`ListColumn`].
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self {
|
||||||
|
self.children.push(ListEntry {
|
||||||
|
item: item.into_list_item(),
|
||||||
|
item_padding: self.list_item_padding,
|
||||||
|
divider_padding: self.divider_padding,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style variant of this [`ListColumn`].
|
||||||
|
#[inline]
|
||||||
|
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
|
||||||
|
self.style = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||||
|
self.list_item_padding = padding.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn divider_padding(mut self, padding: u16) -> Self {
|
||||||
|
self.divider_padding = padding;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_element(self) -> Element<'a, Message> {
|
||||||
|
let count = self.children.len();
|
||||||
|
let last_index = count.saturating_sub(1);
|
||||||
|
let radius_s = theme::active().cosmic().radius_s();
|
||||||
|
let mut col = column::with_capacity((2 * count).saturating_sub(1));
|
||||||
|
|
||||||
|
// Ensure minimum height of 32
|
||||||
|
let content_row = |content| {
|
||||||
|
row![container(content), vertical().height(32)].align_y(iced::Alignment::Center)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (
|
||||||
|
i,
|
||||||
|
ListEntry {
|
||||||
|
item,
|
||||||
|
item_padding,
|
||||||
|
divider_padding,
|
||||||
|
},
|
||||||
|
) in self.children.into_iter().enumerate()
|
||||||
|
{
|
||||||
|
if i > 0 {
|
||||||
|
col = col
|
||||||
|
.push(container(divider::horizontal::default()).padding([0, divider_padding]));
|
||||||
|
}
|
||||||
|
|
||||||
|
col = match item {
|
||||||
|
ListItem::Element(content) => col.push(
|
||||||
|
content_row(content)
|
||||||
|
.padding(item_padding)
|
||||||
|
.width(Length::Fill),
|
||||||
|
),
|
||||||
|
ListItem::Button(ListButton {
|
||||||
|
content,
|
||||||
|
on_press,
|
||||||
|
selected,
|
||||||
|
}) => col.push(
|
||||||
|
content_row(content)
|
||||||
|
.apply(button::custom)
|
||||||
|
.padding(item_padding)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
.selected(selected)
|
||||||
|
.class(theme::Button::ListItem(get_radius(
|
||||||
|
radius_s,
|
||||||
|
i == 0,
|
||||||
|
i == last_index,
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
col.width(Length::Fill)
|
||||||
|
.apply(container)
|
||||||
|
.class(self.style)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(column: ListColumn<'a, Message>) -> Self {
|
||||||
|
column.into_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_radius(radius: [f32; 4], first: bool, last: bool) -> [f32; 4] {
|
||||||
|
match (first, last) {
|
||||||
|
(true, true) => radius,
|
||||||
|
(true, false) => [radius[0], radius[1], 0.0, 0.0],
|
||||||
|
(false, true) => [0.0, 0.0, radius[2], radius[3]],
|
||||||
|
(false, false) => [0.0, 0.0, 0.0, 0.0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub mod column;
|
pub mod list_column;
|
||||||
|
|
||||||
pub use self::column::{ListColumn, list_column};
|
pub use self::list_column::{ListButton, ListColumn, button, list_column};
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ use std::f32::consts::PI;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const MIN_ANGLE: Radians = Radians(PI / 8.0);
|
const MIN_ANGLE: Radians = Radians(PI / 8.0);
|
||||||
const WRAP_ANGLE: Radians = Radians(2.0 * PI - PI / 4.0);
|
|
||||||
const BASE_ROTATION_SPEED: u32 = u32::MAX / 80;
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Circular<Theme>
|
pub struct Circular<Theme>
|
||||||
|
|
@ -83,6 +81,12 @@ where
|
||||||
self.progress = Some(progress.clamp(0.0, 1.0));
|
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) {
|
||||||
|
let cap_angle = self.bar_height / track_radius;
|
||||||
|
let gap = MIN_ANGLE.0.max(cap_angle);
|
||||||
|
(gap - cap_angle, 2.0 * PI - gap * 2.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Default for Circular<Theme>
|
impl<Theme> Default for Circular<Theme>
|
||||||
|
|
@ -122,7 +126,7 @@ impl Default for Animation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animation {
|
impl Animation {
|
||||||
fn next(&self, additional_rotation: u32, now: Instant) -> Self {
|
fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Expanding { rotation, .. } => Self::Contracting {
|
Self::Expanding { rotation, .. } => Self::Contracting {
|
||||||
start: now,
|
start: now,
|
||||||
|
|
@ -133,9 +137,9 @@ impl Animation {
|
||||||
Self::Contracting { rotation, .. } => Self::Expanding {
|
Self::Contracting { rotation, .. } => Self::Expanding {
|
||||||
start: now,
|
start: now,
|
||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
rotation: rotation.wrapping_add(BASE_ROTATION_SPEED.wrapping_add(
|
rotation: rotation.wrapping_add(
|
||||||
(f64::from(WRAP_ANGLE / (2.0 * Radians::PI)) * f64::from(u32::MAX)) as u32,
|
(f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32,
|
||||||
)),
|
),
|
||||||
last: now,
|
last: now,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +161,7 @@ impl Animation {
|
||||||
&self,
|
&self,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
rotation_duration: Duration,
|
rotation_duration: Duration,
|
||||||
|
wrap_angle: f32,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let elapsed = now.duration_since(self.start());
|
let elapsed = now.duration_since(self.start());
|
||||||
|
|
@ -165,7 +170,7 @@ impl Animation {
|
||||||
* (u32::MAX) as f32) as u32;
|
* (u32::MAX) as f32) as u32;
|
||||||
|
|
||||||
match elapsed {
|
match elapsed {
|
||||||
elapsed if elapsed > cycle_duration => self.next(additional_rotation, now),
|
elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now),
|
||||||
_ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now),
|
_ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,10 +272,13 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
state.animation =
|
let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height);
|
||||||
state
|
state.animation = state.animation.timed_transition(
|
||||||
.animation
|
self.cycle_duration,
|
||||||
.timed_transition(self.cycle_duration, self.rotation_duration, *now);
|
self.rotation_duration,
|
||||||
|
wrap_angle,
|
||||||
|
*now,
|
||||||
|
);
|
||||||
|
|
||||||
state.cache.clear();
|
state.cache.clear();
|
||||||
shell.request_redraw();
|
shell.request_redraw();
|
||||||
|
|
@ -380,22 +388,23 @@ where
|
||||||
} else {
|
} else {
|
||||||
let mut builder = canvas::path::Builder::new();
|
let mut builder = canvas::path::Builder::new();
|
||||||
|
|
||||||
let start = Radians(state.animation.rotation() * 2.0 * PI);
|
let start = state.animation.rotation() * 2.0 * PI;
|
||||||
|
let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius);
|
||||||
let (start_angle, end_angle) = match state.animation {
|
let (start_angle, end_angle) = match state.animation {
|
||||||
Animation::Expanding { progress, .. } => (
|
Animation::Expanding { progress, .. } => (
|
||||||
start,
|
start,
|
||||||
start + MIN_ANGLE + WRAP_ANGLE * (smootherstep(progress)),
|
start + min_angle + wrap_angle * smootherstep(progress),
|
||||||
),
|
),
|
||||||
Animation::Contracting { progress, .. } => (
|
Animation::Contracting { progress, .. } => (
|
||||||
start + WRAP_ANGLE * (smootherstep(progress)),
|
start + wrap_angle * smootherstep(progress),
|
||||||
start + MIN_ANGLE + WRAP_ANGLE,
|
start + min_angle + wrap_angle,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
builder.arc(canvas::path::Arc {
|
builder.arc(canvas::path::Arc {
|
||||||
center: frame.center(),
|
center: frame.center(),
|
||||||
radius: track_radius,
|
radius: track_radius,
|
||||||
start_angle,
|
start_angle: Radians(start_angle),
|
||||||
end_angle,
|
end_angle: Radians(end_angle),
|
||||||
});
|
});
|
||||||
|
|
||||||
let bar_path = builder.build();
|
let bar_path = builder.build();
|
||||||
|
|
@ -410,23 +419,23 @@ where
|
||||||
let mut builder = canvas::path::Builder::new();
|
let mut builder = canvas::path::Builder::new();
|
||||||
|
|
||||||
// get center of end of arc for rounded cap
|
// get center of end of arc for rounded cap
|
||||||
let end_center = frame.center()
|
let end_center =
|
||||||
+ Vector::new(end_angle.0.cos(), end_angle.0.sin()) * track_radius;
|
frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
|
||||||
builder.arc(canvas::path::Arc {
|
builder.arc(canvas::path::Arc {
|
||||||
center: end_center,
|
center: end_center,
|
||||||
radius: self.bar_height / 2.0,
|
radius: self.bar_height / 2.0,
|
||||||
start_angle: Radians(end_angle.0),
|
start_angle: Radians(end_angle),
|
||||||
end_angle: Radians(end_angle.0 + PI),
|
end_angle: Radians(end_angle + PI),
|
||||||
});
|
});
|
||||||
|
|
||||||
// get center of start of arc for rounded cap
|
// get center of start of arc for rounded cap
|
||||||
let start_center = frame.center()
|
let start_center = frame.center()
|
||||||
+ Vector::new(start_angle.0.cos(), start_angle.0.sin()) * track_radius;
|
+ Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
|
||||||
builder.arc(canvas::path::Arc {
|
builder.arc(canvas::path::Arc {
|
||||||
center: start_center,
|
center: start_center,
|
||||||
radius: self.bar_height / 2.0,
|
radius: self.bar_height / 2.0,
|
||||||
start_angle: Radians(start_angle.0 - PI),
|
start_angle: Radians(start_angle - PI),
|
||||||
end_angle: Radians(start_angle.0),
|
end_angle: Radians(start_angle),
|
||||||
});
|
});
|
||||||
|
|
||||||
let cap_path = builder.build();
|
let cap_path = builder.build();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Create choices using radio buttons.
|
//! Create choices using radio buttons.
|
||||||
use crate::Theme;
|
use crate::{Theme, theme};
|
||||||
use iced::border;
|
use iced::border;
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::layout;
|
use iced_core::layout;
|
||||||
|
|
@ -92,7 +92,7 @@ where
|
||||||
{
|
{
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
on_click: Message,
|
on_click: Message,
|
||||||
label: Element<'a, Message, Theme, Renderer>,
|
label: Option<Element<'a, Message, Theme, Renderer>>,
|
||||||
width: Length,
|
width: Length,
|
||||||
size: f32,
|
size: f32,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
|
|
@ -106,9 +106,6 @@ where
|
||||||
/// The default size of a [`Radio`] button.
|
/// The default size of a [`Radio`] button.
|
||||||
pub const DEFAULT_SIZE: f32 = 16.0;
|
pub const DEFAULT_SIZE: f32 = 16.0;
|
||||||
|
|
||||||
/// The default spacing of a [`Radio`] button.
|
|
||||||
pub const DEFAULT_SPACING: f32 = 8.0;
|
|
||||||
|
|
||||||
/// Creates a new [`Radio`] button.
|
/// Creates a new [`Radio`] button.
|
||||||
///
|
///
|
||||||
/// It expects:
|
/// It expects:
|
||||||
|
|
@ -126,10 +123,29 @@ where
|
||||||
Radio {
|
Radio {
|
||||||
is_selected: Some(value) == selected,
|
is_selected: Some(value) == selected,
|
||||||
on_click: f(value),
|
on_click: f(value),
|
||||||
label: label.into(),
|
label: Some(label.into()),
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
size: Self::DEFAULT_SIZE,
|
size: Self::DEFAULT_SIZE,
|
||||||
spacing: Self::DEFAULT_SPACING,
|
spacing: theme::spacing().space_xs as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Radio`] button without a label.
|
||||||
|
///
|
||||||
|
/// This is intended for internal use with the settings item builder,
|
||||||
|
/// where the label comes from the settings item title instead.
|
||||||
|
pub(crate) fn new_no_label<V, F>(value: V, selected: Option<V>, f: F) -> Self
|
||||||
|
where
|
||||||
|
V: Eq + Copy,
|
||||||
|
F: FnOnce(V) -> Message,
|
||||||
|
{
|
||||||
|
Radio {
|
||||||
|
is_selected: Some(value) == selected,
|
||||||
|
on_click: f(value),
|
||||||
|
label: None,
|
||||||
|
width: Length::Shrink,
|
||||||
|
size: Self::DEFAULT_SIZE,
|
||||||
|
spacing: theme::spacing().space_xs as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,11 +177,17 @@ where
|
||||||
Renderer: iced_core::Renderer,
|
Renderer: iced_core::Renderer,
|
||||||
{
|
{
|
||||||
fn children(&self) -> Vec<Tree> {
|
fn children(&self) -> Vec<Tree> {
|
||||||
vec![Tree::new(&self.label)]
|
if let Some(label) = &self.label {
|
||||||
|
vec![Tree::new(label)]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
tree.diff_children(std::slice::from_mut(&mut self.label));
|
if let Some(label) = &mut self.label {
|
||||||
|
tree.diff_children(std::slice::from_mut(label));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
Size {
|
Size {
|
||||||
|
|
@ -180,16 +202,20 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
|
if let Some(label) = &mut self.label {
|
||||||
layout::next_to_each_other(
|
layout::next_to_each_other(
|
||||||
&limits.width(self.width),
|
&limits.width(self.width),
|
||||||
self.spacing,
|
self.spacing,
|
||||||
|_| layout::Node::new(Size::new(self.size, self.size)),
|
|_| layout::Node::new(Size::new(self.size, self.size)),
|
||||||
|limits| {
|
|limits| {
|
||||||
self.label
|
label
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.layout(&mut tree.children[0], renderer, limits)
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
layout::Node::new(Size::new(self.size, self.size))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -199,13 +225,15 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||||
) {
|
) {
|
||||||
self.label.as_widget_mut().operate(
|
if let Some(label) = &mut self.label {
|
||||||
|
label.as_widget_mut().operate(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout.children().nth(1).unwrap(),
|
layout.children().nth(1).unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
operation,
|
operation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -218,7 +246,8 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
self.label.as_widget_mut().update(
|
if let Some(label) = &mut self.label {
|
||||||
|
label.as_widget_mut().update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
layout.children().nth(1).unwrap(),
|
layout.children().nth(1).unwrap(),
|
||||||
|
|
@ -228,14 +257,14 @@ where
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if !shell.is_event_captured() {
|
if !shell.is_event_captured() {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerLifted { .. }) => {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
shell.publish(self.on_click.clone());
|
shell.publish(self.on_click.clone());
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -253,13 +282,17 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> mouse::Interaction {
|
) -> mouse::Interaction {
|
||||||
let interaction = self.label.as_widget().mouse_interaction(
|
let interaction = if let Some(label) = &self.label {
|
||||||
|
label.as_widget().mouse_interaction(
|
||||||
&tree.children[0],
|
&tree.children[0],
|
||||||
layout.children().nth(1).unwrap(),
|
layout.children().nth(1).unwrap(),
|
||||||
cursor,
|
cursor,
|
||||||
viewport,
|
viewport,
|
||||||
renderer,
|
renderer,
|
||||||
);
|
)
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
};
|
||||||
|
|
||||||
if interaction == mouse::Interaction::default() {
|
if interaction == mouse::Interaction::default() {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
|
|
@ -284,8 +317,6 @@ where
|
||||||
) {
|
) {
|
||||||
let is_mouse_over = cursor.is_over(layout.bounds());
|
let is_mouse_over = cursor.is_over(layout.bounds());
|
||||||
|
|
||||||
let mut children = layout.children();
|
|
||||||
|
|
||||||
let custom_style = if is_mouse_over {
|
let custom_style = if is_mouse_over {
|
||||||
theme.style(
|
theme.style(
|
||||||
&(),
|
&(),
|
||||||
|
|
@ -302,16 +333,21 @@ where
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
let (dot_bounds, label_layout) = if self.label.is_some() {
|
||||||
let layout = children.next().unwrap();
|
let mut children = layout.children();
|
||||||
let bounds = layout.bounds();
|
let dot_bounds = children.next().unwrap().bounds();
|
||||||
|
(dot_bounds, children.next())
|
||||||
|
} else {
|
||||||
|
(layout.bounds(), None)
|
||||||
|
};
|
||||||
|
|
||||||
let size = bounds.width;
|
{
|
||||||
|
let size = dot_bounds.width;
|
||||||
let dot_size = 6.0;
|
let dot_size = 6.0;
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds,
|
bounds: dot_bounds,
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: (size / 2.0).into(),
|
radius: (size / 2.0).into(),
|
||||||
width: custom_style.border_width,
|
width: custom_style.border_width,
|
||||||
|
|
@ -326,8 +362,8 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + (size - dot_size) / 2.0,
|
x: dot_bounds.x + (size - dot_size) / 2.0,
|
||||||
y: bounds.y + (size - dot_size) / 2.0,
|
y: dot_bounds.y + (size - dot_size) / 2.0,
|
||||||
width: dot_size,
|
width: dot_size,
|
||||||
height: dot_size,
|
height: dot_size,
|
||||||
},
|
},
|
||||||
|
|
@ -339,9 +375,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if let (Some(label), Some(label_layout)) = (&self.label, label_layout) {
|
||||||
let label_layout = children.next().unwrap();
|
label.as_widget().draw(
|
||||||
self.label.as_widget().draw(
|
|
||||||
&tree.children[0],
|
&tree.children[0],
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
|
|
@ -361,7 +396,7 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||||
self.label.as_widget_mut().overlay(
|
self.label.as_mut()?.as_widget_mut().overlay(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout.children().nth(1).unwrap(),
|
layout.children().nth(1).unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -377,7 +412,8 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
) {
|
) {
|
||||||
self.label.as_widget().drag_destinations(
|
if let Some(label) = &self.label {
|
||||||
|
label.as_widget().drag_destinations(
|
||||||
&state.children[0],
|
&state.children[0],
|
||||||
layout.children().nth(1).unwrap(),
|
layout.children().nth(1).unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -385,6 +421,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Theme, Renderer>
|
for Element<'a, Message, Theme, Renderer>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Element, Theme, theme,
|
Element, Theme, theme,
|
||||||
widget::{FlexRow, Row, column, container, flex_row, row, text},
|
widget::{FlexRow, Row, column, container, flex_row, list, row, text},
|
||||||
};
|
};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced_core::{Length, text::Wrapping};
|
use iced_core::{Length, text::Wrapping};
|
||||||
|
|
@ -103,7 +103,7 @@ pub struct Item<'a, Message> {
|
||||||
icon: Option<Element<'a, Message>>,
|
icon: Option<Element<'a, Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> Item<'a, Message> {
|
impl<'a, Message: Clone + 'static> Item<'a, Message> {
|
||||||
/// Assigns a control to the item.
|
/// Assigns a control to the item.
|
||||||
pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message, Theme> {
|
pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message, Theme> {
|
||||||
item_row(self.control_(widget.into()))
|
item_row(self.control_(widget.into()))
|
||||||
|
|
@ -114,39 +114,109 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
||||||
flex_item_row(self.control_(widget.into()))
|
flex_item_row(self.control_(widget.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(never)]
|
fn label(self) -> Element<'a, Message> {
|
||||||
fn control_(self, widget: Element<'a, Message>) -> Vec<Element<'a, Message>> {
|
|
||||||
let mut contents = Vec::with_capacity(4);
|
|
||||||
|
|
||||||
if let Some(icon) = self.icon {
|
|
||||||
contents.push(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(description) = self.description {
|
if let Some(description) = self.description {
|
||||||
let column = column::with_capacity(2)
|
column::with_capacity(2)
|
||||||
.spacing(2)
|
.spacing(2)
|
||||||
.push(text::body(self.title).wrapping(Wrapping::Word))
|
.push(text::body(self.title).wrapping(Wrapping::Word))
|
||||||
.push(text::caption(description).wrapping(Wrapping::Word))
|
.push(text::caption(description).wrapping(Wrapping::Word))
|
||||||
.width(Length::Fill);
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
contents.push(column.into());
|
|
||||||
} else {
|
} else {
|
||||||
contents.push(text(self.title).width(Length::Fill).into());
|
text(self.title).width(Length::Fill).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn control_(mut self, widget: Element<'a, Message>) -> Vec<Element<'a, Message>> {
|
||||||
|
let mut contents = Vec::with_capacity(3);
|
||||||
|
if let Some(icon) = self.icon.take() {
|
||||||
|
contents.push(icon);
|
||||||
|
}
|
||||||
|
contents.push(self.label());
|
||||||
contents.push(widget);
|
contents.push(widget);
|
||||||
contents
|
contents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn control_start(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message, Theme> {
|
||||||
|
item_row(vec![widget.into(), self.label()])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggler(
|
pub fn toggler(
|
||||||
self,
|
self,
|
||||||
is_checked: bool,
|
is_checked: bool,
|
||||||
message: impl Fn(bool) -> Message + 'static,
|
message: impl Fn(bool) -> Message + 'static,
|
||||||
) -> Row<'a, Message, Theme> {
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message(!is_checked);
|
||||||
|
list::button(
|
||||||
self.control(
|
self.control(
|
||||||
crate::widget::toggler(is_checked)
|
crate::widget::toggler(is_checked)
|
||||||
.width(Length::Shrink)
|
.width(Length::Shrink)
|
||||||
.on_toggle(message),
|
.on_toggle(message),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
.on_press(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggler_maybe(
|
||||||
|
self,
|
||||||
|
is_checked: bool,
|
||||||
|
message: Option<impl Fn(bool) -> Message + 'static>,
|
||||||
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message.as_ref().map(|f| f(!is_checked));
|
||||||
|
list::button(
|
||||||
|
self.control(
|
||||||
|
crate::widget::toggler(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle_maybe(message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkbox(
|
||||||
|
self,
|
||||||
|
is_checked: bool,
|
||||||
|
message: impl Fn(bool) -> Message + 'static,
|
||||||
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message(!is_checked);
|
||||||
|
list::button(
|
||||||
|
self.control_start(
|
||||||
|
crate::widget::checkbox(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle(message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_press(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkbox_maybe(
|
||||||
|
self,
|
||||||
|
is_checked: bool,
|
||||||
|
message: Option<impl Fn(bool) -> Message + 'static>,
|
||||||
|
) -> list::ListButton<'a, Message> {
|
||||||
|
let on_press = message.as_ref().map(|f| f(!is_checked));
|
||||||
|
list::button(
|
||||||
|
self.control_start(
|
||||||
|
crate::widget::checkbox(is_checked)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.on_toggle_maybe(message),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_press_maybe(on_press)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn radio<V, F>(self, value: V, selected: Option<V>, f: F) -> list::ListButton<'a, Message>
|
||||||
|
where
|
||||||
|
V: Eq + Copy,
|
||||||
|
F: Fn(V) -> Message,
|
||||||
|
{
|
||||||
|
let on_press = f(value);
|
||||||
|
list::button(
|
||||||
|
self.control_start(crate::widget::radio::Radio::new_no_label(
|
||||||
|
value, selected, f,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.on_press(on_press)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,24 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
use crate::widget::{ListColumn, column, text};
|
use crate::widget::list_column::IntoListItem;
|
||||||
|
use crate::widget::{ListColumn, column, list_column, text};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A section within a settings view column.
|
/// A section within a settings view column.
|
||||||
pub fn section<'a, Message: 'static>() -> Section<'a, Message> {
|
pub fn section<'a, Message: Clone + 'static>() -> Section<'a, Message> {
|
||||||
with_column(ListColumn::default())
|
with_column(ListColumn::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A section with a pre-defined list column of a given capacity.
|
||||||
|
pub fn with_capacity<'a, Message: Clone + 'static>(capacity: usize) -> Section<'a, Message> {
|
||||||
|
with_column(list_column::with_capacity(capacity))
|
||||||
|
}
|
||||||
|
|
||||||
/// A section with a pre-defined list column.
|
/// A section with a pre-defined list column.
|
||||||
pub fn with_column<Message: 'static>(children: ListColumn<'_, Message>) -> Section<'_, Message> {
|
pub fn with_column<Message: Clone + 'static>(
|
||||||
|
children: ListColumn<'_, Message>,
|
||||||
|
) -> Section<'_, Message> {
|
||||||
Section {
|
Section {
|
||||||
header: None,
|
header: None,
|
||||||
children,
|
children,
|
||||||
|
|
@ -24,9 +32,9 @@ pub struct Section<'a, Message> {
|
||||||
children: ListColumn<'a, Message>,
|
children: ListColumn<'a, Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> Section<'a, Message> {
|
impl<'a, Message: Clone + 'static> Section<'a, Message> {
|
||||||
/// Define an optional title for the section.
|
/// Define an optional title for the section.
|
||||||
pub fn title(mut self, title: impl Into<Cow<'a, str>>) -> Self {
|
pub fn title(self, title: impl Into<Cow<'a, str>>) -> Self {
|
||||||
self.header(text::heading(title.into()))
|
self.header(text::heading(title.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,13 +46,13 @@ impl<'a, Message: 'static> Section<'a, Message> {
|
||||||
|
|
||||||
/// Add a child element to the section's list column.
|
/// Add a child element to the section's list column.
|
||||||
#[allow(clippy::should_implement_trait)]
|
#[allow(clippy::should_implement_trait)]
|
||||||
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
|
pub fn add(mut self, item: impl IntoListItem<'a, Message>) -> Self {
|
||||||
self.children = self.children.add(item.into());
|
self.children = self.children.add(item);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a child element to the section's list column, if `Some`.
|
/// Add a child element to the section's list column, if `Some`.
|
||||||
pub fn add_maybe(self, item: Option<impl Into<Element<'a, Message>>>) -> Self {
|
pub fn add_maybe(self, item: Option<impl IntoListItem<'a, Message>>) -> Self {
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
self.add(item)
|
self.add(item)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -55,13 +63,13 @@ impl<'a, Message: 'static> Section<'a, Message> {
|
||||||
/// Extends the [`Section`] with the given children.
|
/// Extends the [`Section`] with the given children.
|
||||||
pub fn extend(
|
pub fn extend(
|
||||||
self,
|
self,
|
||||||
children: impl IntoIterator<Item = impl Into<Element<'a, Message>>>,
|
children: impl IntoIterator<Item = impl IntoListItem<'a, Message>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
children.into_iter().fold(self, Self::add)
|
children.into_iter().fold(self, Self::add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
impl<'a, Message: Clone + 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
||||||
fn from(data: Section<'a, Message>) -> Self {
|
fn from(data: Section<'a, Message>) -> Self {
|
||||||
column::with_capacity(2)
|
column::with_capacity(2)
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,10 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> tree::State {
|
fn state(&self) -> tree::State {
|
||||||
tree::State::new(State::default())
|
tree::State::new(State {
|
||||||
|
prev_toggled: self.is_toggled,
|
||||||
|
..State::default()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<Id> {
|
fn id(&self) -> Option<Id> {
|
||||||
|
|
@ -238,6 +241,14 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
|
// animate external changes
|
||||||
|
if state.prev_toggled != self.is_toggled {
|
||||||
|
state.anim.changed(self.duration);
|
||||||
|
shell.request_redraw();
|
||||||
|
state.prev_toggled = self.is_toggled;
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
|
|
@ -246,6 +257,7 @@ impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a,
|
||||||
if mouse_over {
|
if mouse_over {
|
||||||
shell.publish((on_toggle)(!self.is_toggled));
|
shell.publish((on_toggle)(!self.is_toggled));
|
||||||
state.anim.changed(self.duration);
|
state.anim.changed(self.duration);
|
||||||
|
state.prev_toggled = !self.is_toggled;
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -430,4 +442,5 @@ pub fn next_to_each_other(
|
||||||
pub struct State {
|
pub struct State {
|
||||||
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
||||||
anim: anim::State,
|
anim: anim::State,
|
||||||
|
prev_toggled: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue