Open mmstick opened 3 years ago
I'm not sure how feasible it would be, but perhaps writing iced backends for native toolkits of a platform (qt, gtk, etc.) would work? I know druid supports native platform toolkits so it should be possible.
Let's say that I have an OS, and I want to build that OS around a toolkit that isn't GTK or Qt. I'm already using GTK for building applications in this OS, but I want to see if there are better solutions that I could use should I want to transition away from GTK. So what I want isn't to use a toolkit that has a reliance on GTK or Qt, but a toolkit that I can use as an alternative to them. But that would require that such theoretical toolkit supports defining system themes.
Ah so you mean as in, say, something like a "theming backend", which could perhaps support using GTK themes, or Qt themes etc.?
Having something like /etc/iced/settings.{ron|toml}
that supports defining a theme by name, and having themes placed in /usr/share/themes/{NameOfTheme}/iced/
, using whatever format works best for iced. This way, if I develop an application for this OS, it would use the system theme by default, but users could change the system theme to one they created. Applications could extend the theme or opt into their own custom theme at the application level.
I guess to be clear, support for GTK and Qt themes is not something I'd expect. Instead, a theme format specific to Iced. I don't generally think it wise to try to guess color schemes and layouts from a GTK theme.
I think it sounds like a good idea! I am not happy with our current default theme. This would be part of the efforts to improve it.
We could make iced
search for user settings to obtain the default theme, instead of using the currently hardcoded one. For this, we would need to change the widgets slightly and pass the default theme around when rendering, since it will be chosen at runtime during initialization now.
Where are the hard coded values? I am willing to try and make a draft for this functionality.
As mentioned in https://github.com/iced-rs/iced/discussions/1257, I'd love to take part in implementing this by tackling an important precursor, which is passing around a default styling/theme at runtime. The right place seems to be adding a widget_stylesheet: dyn WidgetStyleSheet
to renderer::Style
for each widget, but I'm not sure. Doing some experimenting today but would love some input/ideas
Update: thats actually kinda a bad place for it to go, especially if we want the user to be able to change the theme at runtime. Current implementation adds fn styling(&self) -> renderer::Style
to Application et. al. Will have a demo in a lil bit
Took the styling
example project and created an application_theme
example. Unlike the styling
example, the widgets don't require individual calls to .style(SomeStyleSheet)
other than the one button that does so in order to show that custom styling still works. All of the structs that implement the various widgets' respective StyleSheet
traits are still present but they are used to create a renderer::Style
that is returned by styling()
and passed through to all Widget::draw()
calls. The .styling()
impl for the root struct that implements Sandbox
is simply:
fn styling(&self) -> Style { self.theme.into() }
as the Theme
enum has an impl From<Theme> for Style
which returns the renderer::Style
for the theme selected.
renderer::Style
to renderer::Theme
and update params/docs/etc. accordinglystyling
example as it doesn't really show anything special anymoreIf/when this gets merged in all that will be left is to create platform-specific impl StyleSheet for Default
for the various widgets.
Hey what's the current status for this @emann ? I'd could really use this. If I may suggest it would be really nice to have just a tiny subset of css (using pest or nom) to configure the themes. I am currently using toml/ron to let user configure iced styling for an app. Not having color syntax highlight is a pain.
Just to add to this. There is a crate called dark-light which can detect the system theme across all platforms(not sure about web though). Using it might be feasible.
The following sample code, using the dark-light crate, works but detects system theme only once when the app is launched.
use iced::{Element, Sandbox, Settings, Theme};
pub fn main() -> iced::Result {
Hello::run(Settings::default())
}
struct Hello;
impl Sandbox for Hello {
type Message = ();
fn new() -> Self {
Self
}
fn title(&self) -> String {
String::from("A cool application")
}
fn update(&mut self, _message: Self::Message) {}
fn view(&self) -> Element<Self::Message> {
"Hello, world!".into()
}
fn theme(&self) -> Theme {
match dark_light::detect() {
dark_light::Mode::Light | dark_light::Mode::Default => Theme::Light,
dark_light::Mode::Dark => Theme::Dark,
}
}
}
In the following sample code, the app theme is aligned to the system theme only when the counter is updated.
use iced::widget::{button, column, text};
use iced::{Alignment, Element, Sandbox, Settings, Theme};
pub fn main() -> iced::Result {
Counter::run(Settings::default())
}
struct Counter {
value: i32,
}
#[derive(Debug, Clone, Copy)]
enum Message {
IncrementPressed,
DecrementPressed,
}
impl Sandbox for Counter {
type Message = Message;
fn new() -> Self {
Self { value: 0 }
}
fn title(&self) -> String {
String::from("Counter - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::IncrementPressed => {
self.value += 1;
}
Message::DecrementPressed => {
self.value -= 1;
}
}
}
fn view(&self) -> Element<Message> {
column![
button("Increment").on_press(Message::IncrementPressed),
text(self.value).size(50),
button("Decrement").on_press(Message::DecrementPressed)
]
.padding(20)
.align_items(Alignment::Center)
.into()
}
fn theme(&self) -> Theme {
match dark_light::detect() {
dark_light::Mode::Light | dark_light::Mode::Default => Theme::Light,
dark_light::Mode::Dark => Theme::Dark,
}
}
}
It would be nice to be able to detect system theme changes automatically and keep the app theme aligned to it.
It would be nice to be able to detect system theme changes automatically and keep the app theme aligned to it.
I implemented Application::subscription
:
fn subscription(&self) -> iced::Subscription<Self::Message> {
iced_native::subscription::events().map(Message::EventOccurred)
}
And that meant it would auto update. Though I'm not sure what the implications of that are.
It should be fairly easy to create a subscription::channel
that runs dark_light::detect
in a loop
every minute or so and publishes Mode
changes to the application.
Could system theme changes be detected by listening to an external event?
It should be fairly easy to create a
subscription::channel
that runsdark_light::detect
in aloop
every minute or so and publishesMode
changes to the application.
I'm trying to implement what suggested. I looked at the iced::subscription::channel
documentation and at the websocket
example, but sorry I'm having a hard time figuring out how to proceed...
Here is a sample code, with an event subscription already implemented, and I would like to add a channel subscription for dark_light
theme detection, can someone kindly help me out?
use std::path::PathBuf;
use iced::widget::text;
use iced::{event, executor, subscription, window};
use iced::{Application, Command, Element, Event, Settings, Subscription, Theme};
pub fn main() -> iced::Result {
FileDrop::run(Settings::default())
}
struct FileDrop {
text: String,
theme: Theme,
}
#[derive(Debug)]
enum Message {
FileDropped(PathBuf),
}
impl Application for FileDrop {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (FileDrop, Command<Self::Message>) {
(
FileDrop {
text: String::from("Drop a file here!"),
theme: system_theme_mode(),
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("File Drop")
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::FileDropped(file) => {
self.text = format!("Dropped file:\n{}", file.to_string_lossy());
Command::none()
}
}
}
fn view(&self) -> Element<Self::Message> {
text(&self.text).into()
}
fn theme(&self) -> Theme {
self.theme.clone()
}
fn subscription(&self) -> Subscription<Self::Message> {
subscription::events_with(|event, status| match (event, status) {
(Event::Window(window::Event::FileDropped(file)), event::Status::Ignored) => {
Some(Message::FileDropped(file))
}
_ => None,
})
}
}
fn system_theme_mode() -> Theme {
match dark_light::detect() {
dark_light::Mode::Light | dark_light::Mode::Default => Theme::Light,
dark_light::Mode::Dark => Theme::Dark,
}
}
I think I finally found my solution, I report it here in case it was useful for others:
use iced::widget::text;
use iced::{event, executor, subscription, time, window};
use iced::{Application, Command, Element, Event, Settings, Subscription, Theme};
use std::path::PathBuf;
fn main() -> iced::Result {
FileDrop::run(Settings::default())
}
struct FileDrop {
text: String,
theme: Theme,
}
#[derive(Debug)]
enum Message {
FileDropped(PathBuf),
SystemThemeMode(Theme),
}
impl Application for FileDrop {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (FileDrop, Command<Self::Message>) {
(
FileDrop {
text: String::from("Drop a file here!"),
theme: system_theme_mode(),
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("File Drop")
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::FileDropped(file) => {
self.text = format!("Dropped file:\n{}", file.to_string_lossy());
Command::none()
}
Message::SystemThemeMode(theme) => {
self.theme = theme;
Command::none()
}
}
}
fn view(&self) -> Element<Self::Message> {
Element::new(text(&self.text))
}
fn theme(&self) -> Self::Theme {
self.theme.clone()
}
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::batch([
subscription::events_with(|event, status| match (event, status) {
(Event::Window(window::Event::FileDropped(file)), event::Status::Ignored) => {
Some(Message::FileDropped(file))
}
_ => None,
}),
time::every(time::Duration::from_secs(60))
.map(|_| Message::SystemThemeMode(system_theme_mode())),
])
}
}
fn system_theme_mode() -> Theme {
match dark_light::detect() {
dark_light::Mode::Light | dark_light::Mode::Default => Theme::Light,
dark_light::Mode::Dark => Theme::Dark,
}
}
This requires either one of the following iced
features to be enabled: tokio
, async-std
, or smol
.
What's the stance on supporting system themes? It's often important to have a consistent UX across the desktop, with all applications sharing a common system theme by default. Will iced support allowing users and distributors to design system themes that can be applied universally to all iced applications?