summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/appearance.rs46
-rw-r--r--src/config.rs46
-rw-r--r--src/dialogs.rs8
-rw-r--r--src/icon.rs10
-rw-r--r--src/main.rs109
-rw-r--r--src/options.rs2
-rw-r--r--src/panes/code_view.rs87
-rw-r--r--src/panes/designer_view.rs53
-rw-r--r--src/panes/element_list.rs16
-rw-r--r--src/panes/style.rs23
-rw-r--r--src/theme.rs425
-rw-r--r--src/types.rs8
-rw-r--r--src/types/project.rs70
-rwxr-xr-xsrc/types/rendered_element.rs158
-rw-r--r--src/widget.rs18
15 files changed, 386 insertions, 693 deletions
diff --git a/src/appearance.rs b/src/appearance.rs
new file mode 100644
index 0000000..78e782d
--- /dev/null
+++ b/src/appearance.rs
@@ -0,0 +1,46 @@
+use std::sync::Arc;
+
+use material_theme::Theme;
+
+pub fn iced_theme_from_str(theme_name: &str) -> iced::Theme {
+ match theme_name {
+ "Light" => iced::Theme::Light,
+ "Dark" => iced::Theme::Dark,
+ "Dracula" => iced::Theme::Dracula,
+ "Nord" => iced::Theme::Nord,
+ "Solarized Light" => iced::Theme::SolarizedLight,
+ "Solarized Dark" => iced::Theme::SolarizedDark,
+ "Gruvbox Light" => iced::Theme::GruvboxLight,
+ "Gruvbox Dark" => iced::Theme::GruvboxDark,
+ "Catppuccin Latte" => iced::Theme::CatppuccinLatte,
+ "Catppuccin Frappé" => iced::Theme::CatppuccinFrappe,
+ "Catppuccin Macchiato" => iced::Theme::CatppuccinMacchiato,
+ "Catppuccin Mocha" => iced::Theme::CatppuccinMocha,
+ "Tokyo Night" => iced::Theme::TokyoNight,
+ "Tokyo Night Storm" => iced::Theme::TokyoNightStorm,
+ "Tokyo Night Light" => iced::Theme::TokyoNightLight,
+ "Kanagawa Wave" => iced::Theme::KanagawaWave,
+ "Kanagawa Dragon" => iced::Theme::KanagawaDragon,
+ "Kanagawa Lotus" => iced::Theme::KanagawaLotus,
+ "Moonfly" => iced::Theme::Moonfly,
+ "Nightfly" => iced::Theme::Nightfly,
+ "Oxocarbon" => iced::Theme::Oxocarbon,
+ "Ferra" => iced::Theme::Ferra,
+ _ => iced::Theme::default(),
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Appearance {
+ pub selected: Theme,
+ pub all: Arc<[Theme]>,
+}
+
+impl Default for Appearance {
+ fn default() -> Self {
+ Self {
+ selected: Theme::default(),
+ all: Theme::ALL.into(),
+ }
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index 1da1239..369a505 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,24 +1,34 @@
// (c) 2022-2024 Cory Forsstrom, Casper Rogild Storm, Calvin Lee, Andrew Baldwin, Reza Alizadeh Majd
// (c) 2024-2025 Polesznyák Márk László
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use material_theme::Theme;
use serde::Deserialize;
use tokio_stream::StreamExt;
use tokio_stream::wrappers::ReadDirStream;
-use crate::theme::{Appearance, Theme, theme_from_str, theme_index};
+use crate::appearance::Appearance;
use crate::{Error, environment};
#[derive(Debug, Clone, Default)]
pub struct Config {
- pub theme: Appearance,
- pub last_project: Option<PathBuf>,
+ appearance: Appearance,
+ last_project: Option<PathBuf>,
}
impl Config {
- pub fn selected_theme(&self) -> iced::Theme {
- self.theme.selected.clone()
+ pub fn selected_theme(&self) -> Theme {
+ self.appearance.selected.clone()
+ }
+
+ pub fn themes(&self) -> Arc<[Theme]> {
+ self.appearance.all.clone()
+ }
+
+ pub fn last_project(&self) -> Option<&Path> {
+ self.last_project.as_deref()
}
pub fn config_dir() -> PathBuf {
@@ -67,15 +77,18 @@ impl Config {
last_project,
} = toml::from_str(content.as_ref())?;
- let theme = Self::load_theme(theme).await.unwrap_or_default();
+ let appearance =
+ Self::load_appearance(&theme).await.unwrap_or_default();
Ok(Self {
- theme,
+ appearance,
last_project,
})
}
- pub async fn load_theme(theme_name: String) -> Result<Appearance, Error> {
+ pub async fn load_appearance(
+ theme_name: &str,
+ ) -> Result<Appearance, Error> {
use tokio::fs;
let read_entry = async move |entry: fs::DirEntry| {
@@ -83,15 +96,16 @@ impl Config {
let theme: Theme = toml::from_str(content.as_ref()).ok()?;
- Some(iced::Theme::from(theme))
+ Some(theme)
};
- let mut selected = Theme::default().into();
- let mut all = iced::Theme::ALL.to_owned();
- all.push(Theme::default().into());
+ let mut selected = Theme::default();
+ let mut all = Theme::ALL.to_owned();
- if theme_index(&theme_name, iced::Theme::ALL).is_some() {
- selected = theme_from_str(None, &theme_name);
+ if let Some(index) =
+ Theme::ALL.iter().position(|t| t.name() == theme_name)
+ {
+ selected = Theme::ALL[index].clone();
}
let mut stream =
@@ -102,7 +116,7 @@ impl Config {
};
if let Some(theme) = read_entry(entry).await {
- if theme.to_string() == theme_name {
+ if theme.name() == theme_name {
selected = theme.clone();
}
all.push(theme);
diff --git a/src/dialogs.rs b/src/dialogs.rs
index 08513fd..c1933ec 100644
--- a/src/dialogs.rs
+++ b/src/dialogs.rs
@@ -1,19 +1,19 @@
-use iced::{Element, Task};
+use iced::Task;
use iced_dialog::button;
use crate::Message;
-use crate::types::{DialogAction, DialogButtons};
+use crate::types::{DialogAction, DialogButtons, Element};
pub const UNSAVED_CHANGES_TITLE: &str = "Unsaved changes";
pub const WARNING_TITLE: &str = "Heads up!";
pub const ERROR_TITLE: &str = "Oops! Something went wrong.";
pub fn ok_button<'a>() -> Element<'a, Message> {
- button("Ok").on_press(Message::DialogOk).into()
+ button("Ok", Message::DialogOk).into()
}
pub fn cancel_button<'a>() -> Element<'a, Message> {
- button("Cancel").on_press(Message::DialogCancel).into()
+ button("Cancel", Message::DialogCancel).into()
}
pub fn error_dialog(description: impl Into<String>) -> Task<Message> {
diff --git a/src/icon.rs b/src/icon.rs
index 9dc0a89..d218943 100644
--- a/src/icon.rs
+++ b/src/icon.rs
@@ -1,8 +1,10 @@
// Generated automatically by iced_fontello at build time.
// Do not edit manually. Source: ../fonts/icons.toml
-// 02c7558d187cdc056fdd0e6a638ef805fa10f5955f834575e51d75acd35bc70e
-use iced::widget::{text, Text};
+// 915ea6b0646871c0f04350f201f27f28881b61f3bd6ef292a415d67a211739c1
use iced::Font;
+use iced::widget::text;
+
+use crate::widget::Text;
pub const FONT: &[u8] = include_bytes!("../fonts/icons.ttf");
@@ -18,6 +20,10 @@ pub fn save<'a>() -> Text<'a> {
icon("\u{1F4BE}")
}
+pub fn switch<'a>() -> Text<'a> {
+ icon("\u{21C6}")
+}
+
fn icon(codepoint: &str) -> Text<'_> {
text(codepoint).font(Font::with_name("icons"))
}
diff --git a/src/main.rs b/src/main.rs
index d5715ef..1ac1d67 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+mod appearance;
mod config;
mod dialogs;
mod environment;
@@ -6,7 +7,6 @@ mod error;
mod icon;
mod options;
mod panes;
-mod theme;
mod types;
mod values;
mod widget;
@@ -24,14 +24,14 @@ use iced::advanced::widget::Id;
use iced::widget::{
Column, container, pane_grid, pick_list, row, text, text_editor,
};
-use iced::{Alignment, Element, Length, Task, Theme, clipboard, keyboard};
+use iced::{Alignment, Length, Task, clipboard, keyboard};
use iced_anim::transition::Easing;
use iced_anim::{Animated, Animation};
use iced_dialog::dialog::Dialog;
+use material_theme::Theme;
use panes::{code_view, designer_view, element_list};
-use tokio::runtime;
use types::{
- Action, DesignerPane, DialogAction, DialogButtons, ElementName, Message,
+ Action, DesignerPane, DialogAction, DialogButtons, Element, Message,
Project,
};
@@ -47,23 +47,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(());
}
- let config_load = {
- let rt = runtime::Builder::new_current_thread()
- .enable_all()
- .build()?;
+ iced::application(
+ IcedBuilder::init,
+ IcedBuilder::update,
+ IcedBuilder::view,
+ )
+ .title(IcedBuilder::title)
+ .font(icon::FONT)
+ .theme(|state| state.theme.value().clone())
+ .subscription(IcedBuilder::subscription)
+ .antialiasing(true)
+ .centered()
+ .run()?;
- rt.block_on(Config::load())
- };
-
- iced::application(App::title, App::update, App::view)
- .font(icon::FONT)
- .theme(|state| state.theme.value().clone())
- .subscription(App::subscription)
- .run_with(move || App::new(config_load))?;
Ok(())
}
-struct App {
+struct IcedBuilder {
is_dirty: bool,
is_loading: bool,
project_path: Option<PathBuf>,
@@ -78,7 +78,6 @@ struct App {
dialog_content: String,
dialog_buttons: DialogButtons,
dialog_action: DialogAction,
- element_list: &'static [ElementName],
editor_content: text_editor::Content,
}
@@ -88,8 +87,8 @@ enum Panes {
ElementList,
}
-impl App {
- fn new(config_load: Result<Config, Error>) -> (Self, Task<Message>) {
+impl IcedBuilder {
+ fn init() -> (Self, Task<Message>) {
let state = pane_grid::State::with_configuration(
pane_grid::Configuration::Split {
axis: pane_grid::Axis::Vertical,
@@ -99,25 +98,9 @@ impl App {
},
);
- let config = Arc::new(config_load.unwrap_or_default());
+ let config = Arc::new(Config::default());
let theme = config.selected_theme();
- let task = if let Some(path) = config.last_project.clone() {
- if path.exists() && path.is_file() {
- Task::perform(
- Project::from_path(path, config.clone()),
- Message::FileOpened,
- )
- } else {
- warning_dialog(format!(
- "The file {} does not exist, or isn't a file.",
- path.to_string_lossy()
- ))
- }
- } else {
- Task::none()
- };
-
(
Self {
is_dirty: false,
@@ -134,10 +117,9 @@ impl App {
dialog_content: String::new(),
dialog_buttons: DialogButtons::None,
dialog_action: DialogAction::None,
- element_list: ElementName::ALL,
editor_content: text_editor::Content::new(),
},
- task,
+ Task::perform(Config::load(), Message::ConfigLoad),
)
}
@@ -163,6 +145,29 @@ impl App {
fn update(&mut self, message: Message) -> Task<Message> {
match message {
+ Message::ConfigLoad(result) => match result {
+ Ok(config) => {
+ self.config = Arc::new(config);
+ self.theme.update(self.config.selected_theme().into());
+
+ return if let Some(path) = self.config.last_project() {
+ if path.exists() && path.is_file() {
+ Task::perform(
+ Project::from_path(path.to_owned()),
+ Message::FileOpened,
+ )
+ } else {
+ warning_dialog(format!(
+ "The file {} does not exist, or isn't a file.",
+ path.to_string_lossy()
+ ))
+ }
+ } else {
+ Task::none()
+ };
+ }
+ Err(error) => return error_dialog(error),
+ },
Message::SwitchTheme(event) => self.theme.update(event),
Message::CopyCode => {
return clipboard::write(self.editor_content.text());
@@ -173,15 +178,13 @@ impl App {
self.editor_content.perform(action);
}
}
- Message::RefreshEditorContent => {
- match self.project.app_code(&self.config) {
- Ok(code) => {
- self.editor_content =
- text_editor::Content::with_text(&code);
- }
- Err(error) => return error_dialog(error),
+ Message::RefreshEditorContent => match self.project.app_code() {
+ Ok(code) => {
+ self.editor_content =
+ text_editor::Content::with_text(&code);
}
- }
+ Err(error) => return error_dialog(error),
+ },
Message::DropNewElement(name, point, _) => {
return iced_drop::zones_on_point(
move |zones| Message::HandleNew(name.clone(), zones),
@@ -277,7 +280,7 @@ impl App {
self.is_dirty = false;
self.is_loading = true;
return Task::perform(
- Project::from_file(self.config.clone()),
+ Project::from_file(),
Message::FileOpened,
)
.chain(close_dialog_task);
@@ -307,7 +310,7 @@ impl App {
self.is_loading = true;
return Task::perform(
- Project::from_file(self.config.clone()),
+ Project::from_file(),
Message::FileOpened,
);
} else {
@@ -392,7 +395,7 @@ impl App {
fn view(&self) -> Element<'_, Message> {
let header = row![pick_list(
- self.config.theme.all.clone(),
+ self.config.themes(),
Some(self.theme.target()),
|theme| Message::SwitchTheme(theme.into())
)]
@@ -405,16 +408,14 @@ impl App {
Panes::Designer => match &self.designer_page {
DesignerPane::DesignerView => designer_view::view(
self.project.element_tree.as_ref(),
- self.project.get_theme(&self.config),
+ self.project.get_theme(),
is_focused,
),
DesignerPane::CodeView => {
code_view::view(&self.editor_content, is_focused)
}
},
- Panes::ElementList => {
- element_list::view(self.element_list, is_focused)
- }
+ Panes::ElementList => element_list::view(is_focused),
}
},
)
diff --git a/src/options.rs b/src/options.rs
index 2dc25d7..931182a 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -258,7 +258,7 @@ impl<Message> ApplyOptions for Row<'_, Message> {
}
}
-impl<Handle> ApplyOptions for Image<Handle> {
+impl<Handle> ApplyOptions for Image<'_, Handle> {
fn apply_options(self, options: BTreeMap<String, Option<String>>) -> Self {
let mut image = self;
diff --git a/src/panes/code_view.rs b/src/panes/code_view.rs
index 551347c..5999b8f 100644
--- a/src/panes/code_view.rs
+++ b/src/panes/code_view.rs
@@ -1,69 +1,90 @@
use iced::advanced::text::highlighter::Format;
-use iced::widget::{Space, button, pane_grid, row, text, text_editor};
-use iced::{Alignment, Background, Border, Font, Length, Theme};
+use iced::border::Radius;
+use iced::widget::{button, pane_grid, row, text, text_editor};
+use iced::{Alignment, Border, Font, Length};
use iced_custom_highlighter::{Highlight, Highlighter, Scope, Settings};
+use material_theme::Theme;
use super::style;
use crate::icon;
use crate::types::{DesignerPane, Message};
use crate::widget::tip;
+// TODO: implement a highlight style for the material theme
fn highlight_style(theme: &Theme, scope: &Scope) -> Format<Font> {
+ let theme = if theme.is_dark() {
+ iced::Theme::SolarizedDark
+ } else {
+ iced::Theme::SolarizedLight
+ };
+
match scope {
Scope::Custom { .. } | Scope::Other => Format {
color: Some(theme.extended_palette().primary.strong.color),
font: None,
},
- _ => Highlight::default_style(theme, scope),
+ _ => Highlight::default_style(&theme, scope),
}
}
pub fn view(
editor_content: &text_editor::Content,
is_focused: bool,
-) -> pane_grid::Content<'_, Message> {
- let title = row![
- text("Generated Code"),
- Space::with_width(Length::Fill),
- tip(
- button(icon::copy())
- .on_press(Message::CopyCode)
- .padding([2, 7])
- .style(button::text),
- "Copy",
- tip::Position::FollowCursor
- ),
- Space::with_width(20),
- button("Switch to Designer view")
- .on_press(Message::SwitchPage(DesignerPane::DesignerView))
- ]
- .align_y(Alignment::Center);
- let title_bar = pane_grid::TitleBar::new(title)
+) -> pane_grid::Content<'_, Message, Theme> {
+ let title_bar = pane_grid::TitleBar::new(text("Generated Code").center())
+ .controls(pane_grid::Controls::dynamic(
+ row![
+ tip(
+ button(icon::copy())
+ .on_press(Message::CopyCode)
+ .padding([2, 7])
+ .style(material_theme::button::text),
+ "Copy",
+ tip::Position::FollowCursor
+ ),
+ button("Switch to Designer view")
+ .on_press(Message::SwitchPage(DesignerPane::DesignerView))
+ ]
+ .spacing(20)
+ .align_y(Alignment::Center),
+ row![
+ tip(
+ button(icon::copy())
+ .on_press(Message::CopyCode)
+ .padding([2, 7])
+ .style(material_theme::button::text),
+ "Copy",
+ tip::Position::FollowCursor
+ ),
+ button(icon::switch())
+ .on_press(Message::SwitchPage(DesignerPane::DesignerView))
+ ]
+ .spacing(20)
+ .align_y(Alignment::Center),
+ ))
.padding(10)
.style(style::title_bar);
+
pane_grid::Content::new(
text_editor(editor_content)
.on_action(Message::EditorAction)
.font(Font::MONOSPACE)
- .highlight_with::<Highlighter>(
+ .highlight_with::<Highlighter<Theme>>(
Settings::new(vec![], highlight_style, "rs"),
Highlight::to_format,
)
.style(|theme, _| {
- let palette = theme.extended_palette();
+ let style = material_theme::text_editor::default(
+ theme,
+ text_editor::Status::Active,
+ );
+
text_editor::Style {
- background: Background::Color(
- palette.background.base.color,
- ),
border: Border {
- radius: 2.0.into(),
- width: 1.0,
- color: palette.background.strong.color,
+ radius: Radius::default(),
+ ..style.border
},
- icon: palette.background.weak.text,
- placeholder: palette.background.strong.color,
- value: palette.background.base.text,
- selection: palette.primary.weak.color,
+ ..style
}
})
.height(Length::Fill)
diff --git a/src/panes/designer_view.rs b/src/panes/designer_view.rs
index 6340f73..0255b40 100644
--- a/src/panes/designer_view.rs
+++ b/src/panes/designer_view.rs
@@ -1,32 +1,55 @@
-use iced::widget::{Space, button, container, pane_grid, row, text, themer};
-use iced::{Alignment, Element, Length};
+use iced::widget::{
+ button, center, container, pane_grid, responsive, row, text, themer,
+};
+use iced::{Alignment, Length};
+use material_theme::Theme;
use super::style;
+use crate::icon;
use crate::types::{DesignerPane, Message, RenderedElement};
pub fn view<'a>(
- element_tree: Option<&RenderedElement>,
+ element_tree: Option<&'a RenderedElement>,
designer_theme: iced::Theme,
is_focused: bool,
-) -> pane_grid::Content<'a, Message> {
- let el_tree: Element<'a, Message> = match element_tree {
- Some(tree) => tree.clone().into(),
- None => text("Open a project or begin creating one").into(),
+) -> pane_grid::Content<'a, Message, Theme> {
+ let el_tree: iced::Element<'a, Message> = match element_tree {
+ Some(tree) => responsive(|size| {
+ center(
+ container(tree.clone())
+ .style(|theme| {
+ container::background(theme.palette().background)
+ })
+ .height(size.height * 0.5)
+ .width(size.height * 0.8),
+ )
+ .into()
+ })
+ .into(),
+ None => center("Open a project or begin creating one").into(),
};
+
let content = container(themer(designer_theme, el_tree))
.id(iced::widget::container::Id::new("drop_zone"))
.height(Length::Fill)
.width(Length::Fill);
- let title = row![
- text("Designer"),
- Space::with_width(Length::Fill),
- button("Switch to Code view")
- .on_press(Message::SwitchPage(DesignerPane::CodeView)),
- ]
- .align_y(Alignment::Center);
- let title_bar = pane_grid::TitleBar::new(title)
+
+ let title_bar = pane_grid::TitleBar::new(text("Designer").center())
+ .controls(pane_grid::Controls::dynamic(
+ row![
+ button("Switch to Code view")
+ .on_press(Message::SwitchPage(DesignerPane::CodeView),)
+ ]
+ .align_y(Alignment::Center),
+ row![
+ button(icon::switch())
+ .on_press(Message::SwitchPage(DesignerPane::CodeView),)
+ ]
+ .align_y(Alignment::Center),
+ ))
.padding(10)
.style(style::title_bar);
+
pane_grid::Content::new(content)
.title_bar(title_bar)
.style(if is_focused {
diff --git a/src/panes/element_list.rs b/src/panes/element_list.rs
index 10eea66..0e5dbfe 100644
--- a/src/panes/element_list.rs
+++ b/src/panes/element_list.rs
@@ -1,17 +1,18 @@
use iced::widget::{Column, column, container, pane_grid, text};
-use iced::{Alignment, Element, Length};
+use iced::{Alignment, Length};
use iced_drop::droppable;
+use material_theme::Theme;
use super::style;
-use crate::types::{ElementName, Message};
+use crate::types::{Element, ElementName, Message};
-fn items_list_view(items: &[ElementName]) -> Element<'_, Message> {
+fn items_list_view<'a>() -> Element<'a, Message> {
let mut column = Column::new()
.spacing(20)
.align_x(Alignment::Center)
.width(Length::Fill);
- for item in items {
+ for item in ElementName::ALL {
column = column.push(
droppable(text(item.clone().to_string())).on_drop(|point, rect| {
Message::DropNewElement(item.clone(), point, rect)
@@ -25,11 +26,8 @@ fn items_list_view(items: &[ElementName]) -> Element<'_, Message> {
.into()
}
-pub fn view(
- element_list: &[ElementName],
- is_focused: bool,
-) -> pane_grid::Content<'_, Message> {
- let items_list = items_list_view(element_list);
+pub fn view<'a>(is_focused: bool) -> pane_grid::Content<'a, Message, Theme> {
+ let items_list = items_list_view();
let content = column![items_list]
.align_x(Alignment::Center)
.height(Length::Fill)
diff --git a/src/panes/style.rs b/src/panes/style.rs
index 1eefb2d..acca6f9 100644
--- a/src/panes/style.rs
+++ b/src/panes/style.rs
@@ -1,24 +1,25 @@
use iced::widget::container::Style;
-use iced::{Border, Theme};
+use iced::{Background, Border};
+use material_theme::Theme;
pub fn title_bar(theme: &Theme) -> Style {
- let palette = theme.extended_palette();
+ let surface = theme.colors().surface;
Style {
- text_color: Some(palette.background.strong.text),
- background: Some(palette.background.strong.color.into()),
+ text_color: Some(surface.on_surface),
+ background: Some(Background::Color(surface.surface_container.high)),
..Default::default()
}
}
pub fn pane_active(theme: &Theme) -> Style {
- let palette = theme.extended_palette();
+ let surface = theme.colors().surface;
Style {
- background: Some(palette.background.weak.color.into()),
+ background: Some(Background::Color(surface.surface_container.low)),
border: Border {
width: 1.0,
- color: palette.background.strong.color,
+ color: surface.surface_container.high,
..Border::default()
},
..Default::default()
@@ -26,13 +27,13 @@ pub fn pane_active(theme: &Theme) -> Style {
}
pub fn pane_focused(theme: &Theme) -> Style {
- let palette = theme.extended_palette();
+ let surface = theme.colors().surface;
Style {
- background: Some(palette.background.weak.color.into()),
+ background: Some(Background::Color(surface.surface_container.low)),
border: Border {
- width: 4.0,
- color: palette.background.strong.color,
+ width: 2.0,
+ color: surface.surface_container.high,
..Border::default()
},
..Default::default()
diff --git a/src/theme.rs b/src/theme.rs
deleted file mode 100644
index a072714..0000000
--- a/src/theme.rs
+++ /dev/null
@@ -1,425 +0,0 @@
-use std::sync::Arc;
-
-use iced::Color;
-use iced::theme::palette::Extended;
-
-use crate::config::Config;
-
-const DEFAULT_THEME_CONTENT: &str =
- include_str!("../assets/themes/rose_pine.toml");
-
-pub fn theme_index(theme_name: &str, slice: &[iced::Theme]) -> Option<usize> {
- slice
- .iter()
- .position(|theme| theme.to_string() == theme_name)
-}
-
-pub fn theme_from_str(
- config: Option<&Config>,
- theme_name: &str,
-) -> iced::Theme {
- match theme_name {
- "Light" => iced::Theme::Light,
- "Dark" => iced::Theme::Dark,
- "Dracula" => iced::Theme::Dracula,
- "Nord" => iced::Theme::Nord,
- "Solarized Light" => iced::Theme::SolarizedLight,
- "Solarized Dark" => iced::Theme::SolarizedDark,
- "Gruvbox Light" => iced::Theme::GruvboxLight,
- "Gruvbox Dark" => iced::Theme::GruvboxDark,
- "Catppuccin Latte" => iced::Theme::CatppuccinLatte,
- "Catppuccin Frappé" => iced::Theme::CatppuccinFrappe,
- "Catppuccin Macchiato" => iced::Theme::CatppuccinMacchiato,
- "Catppuccin Mocha" => iced::Theme::CatppuccinMocha,
- "Tokyo Night" => iced::Theme::TokyoNight,
- "Tokyo Night Storm" => iced::Theme::TokyoNightStorm,
- "Tokyo Night Light" => iced::Theme::TokyoNightLight,
- "Kanagawa Wave" => iced::Theme::KanagawaWave,
- "Kanagawa Dragon" => iced::Theme::KanagawaDragon,
- "Kanagawa Lotus" => iced::Theme::KanagawaLotus,
- "Moonfly" => iced::Theme::Moonfly,
- "Nightfly" => iced::Theme::Nightfly,
- "Oxocarbon" => iced::Theme::Oxocarbon,
- "Ferra" => iced::Theme::Ferra,
- _ => {
- if let Some(config) = config {
- if theme_name == config.theme.selected.to_string() {
- config.theme.selected.clone()
- } else if let Some(index) =
- theme_index(theme_name, &config.theme.all)
- {
- config.theme.all[index].clone()
- } else {
- iced::Theme::default()
- }
- } else {
- iced::Theme::default()
- }
- }
- }
-}
-
-fn palette_to_string(palette: &iced::theme::Palette) -> String {
- format!(
- r"Palette {{
- background: color!(0x{}),
- text: color!(0x{}),
- primary: color!(0x{}),
- success: color!(0x{}),
- danger: color!(0x{}),
- warning: color!(0x{}),
- }}",
- color_to_hex(palette.background),
- color_to_hex(palette.text),
- color_to_hex(palette.primary),
- color_to_hex(palette.success),
- color_to_hex(palette.danger),
- color_to_hex(palette.warning),
- )
-}
-
-fn extended_to_string(extended: &Extended) -> String {
- format!(
- r"
-Extended{{background:Background{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},primary:Primary{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},secondary:Secondary{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},success:Success{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},danger:Danger{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},warning:Warning{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},is_dark:true,}}",
- color_to_hex(extended.background.base.color),
- color_to_hex(extended.background.base.text),
- color_to_hex(extended.background.weak.color),
- color_to_hex(extended.background.weak.text),
- color_to_hex(extended.background.strong.color),
- color_to_hex(extended.background.strong.text),
- color_to_hex(extended.primary.base.color),
- color_to_hex(extended.primary.base.text),
- color_to_hex(extended.primary.weak.color),
- color_to_hex(extended.primary.weak.text),
- color_to_hex(extended.primary.strong.color),
- color_to_hex(extended.primary.strong.text),
- color_to_hex(extended.secondary.base.color),
- color_to_hex(extended.secondary.base.text),
- color_to_hex(extended.secondary.weak.color),
- color_to_hex(extended.secondary.weak.text),
- color_to_hex(extended.secondary.strong.color),
- color_to_hex(extended.secondary.strong.text),
- color_to_hex(extended.success.base.color),
- color_to_hex(extended.success.base.text),
- color_to_hex(extended.success.weak.color),
- color_to_hex(extended.success.weak.text),
- color_to_hex(extended.success.strong.color),
- color_to_hex(extended.success.strong.text),
- color_to_hex(extended.danger.base.color),
- color_to_hex(extended.danger.base.text),
- color_to_hex(extended.danger.weak.color),
- color_to_hex(extended.danger.weak.text),
- color_to_hex(extended.danger.strong.color),
- color_to_hex(extended.danger.strong.text),
- color_to_hex(extended.warning.base.color),
- color_to_hex(extended.warning.base.text),
- color_to_hex(extended.warning.weak.color),
- color_to_hex(extended.warning.weak.text),
- color_to_hex(extended.warning.strong.color),
- color_to_hex(extended.warning.strong.text),
- )
-}
-
-pub fn theme_to_string(theme: &iced::Theme) -> String {
- let palette = theme.palette();
- let extended = theme.extended_palette();
-
- let generated_extended = Extended::generate(palette);
-
- if &generated_extended == extended {
- format!(
- r#"custom(
- "{}".to_string(),
- {}
- )"#,
- theme,
- palette_to_string(&palette)
- )
- } else {
- format!(
- r#"custom_with_fn(
- "{}".to_string(),
- {},
- |_| {}
- )"#,
- theme,
- palette_to_string(&palette),
- extended_to_string(extended)
- )
- }
-}
-
-fn color_to_hex(color: Color) -> String {
- use std::fmt::Write;
-
- let mut hex = String::with_capacity(12);
-
- let [r, g, b, a] = color.into_rgba8();
-
- let _ = write!(&mut hex, "{:02X}", r);
- let _ = write!(&mut hex, "{:02X}", g);
- let _ = write!(&mut hex, "{:02X}", b);
-
- if a < u8::MAX {
- let _ = write!(&mut hex, ", {:.2}", a as f32 / 255.0);
- }
-
- hex
-}
-
-#[derive(Debug, Clone)]
-pub struct Appearance {
- pub selected: iced::Theme,
- pub all: Arc<[iced::Theme]>,
-}
-
-impl Default for Appearance {
- fn default() -> Self {
- Self {
- selected: Theme::default().into(),
- all: {
- let mut themes = iced::Theme::ALL.to_owned();
- themes.push(Theme::default().into());
- themes.into()
- },
- }
- }
-}
-
-#[derive(Debug, serde::Deserialize)]
-pub struct Theme {
- name: String,
- palette: ThemePalette,
- dark: Option<bool>,
- #[serde(flatten)]
- extended: Option<ExtendedThemePalette>,
-}
-
-impl From<Theme> for iced::Theme {
- fn from(value: Theme) -> Self {
- iced::Theme::custom_with_fn(
- value.name.clone(),
- value.palette.clone().into(),
- |_| value.into(),
- )
- }
-}
-
-impl Default for Theme {
- fn default() -> Self {
- toml::from_str(DEFAULT_THEME_CONTENT).expect("parse default theme")
- }
-}
-
-#[derive(Debug, Clone, serde::Deserialize)]
-pub struct ThemePalette {
- #[serde(with = "color_serde")]
- background: Color,
- #[serde(with = "color_serde")]
- text: Color,
- #[serde(with = "color_serde")]
- primary: Color,
- #[serde(with = "color_serde")]
- success: Color,
- #[serde(with = "color_serde")]
- danger: Color,
- #[serde(with = "color_serde")]
- warning: Color,
-}
-
-impl Default for ThemePalette {
- fn default() -> Self {
- let palette = iced::Theme::default().palette();
- Self {
- background: palette.background,
- text: palette.text,
- primary: palette.primary,
- success: palette.success,
- danger: palette.danger,
- warning: palette.warning,
- }
- }
-}
-
-impl From<ThemePalette> for iced::theme::Palette {
- fn from(palette: ThemePalette) -> Self {
- iced::theme::Palette {
- background: palette.background,
- text: palette.text,
- primary: palette.primary,
- success: palette.success,
- danger: palette.danger,
- warning: palette.warning,
- }
- }
-}
-
-impl From<Theme> for Extended {
- fn from(theme: Theme) -> Self {
- let mut extended = Extended::generate(theme.palette.into());
-
- if let Some(is_dark) = theme.dark {
- extended.is_dark = is_dark;
- }
-
- if let Some(extended_palette) = theme.extended {
- if let Some(background) = extended_palette.background {
- if let Some(base) = background.base {
- extended.background.base = base.into();
- }
- if let Some(weak) = background.weak {
- extended.background.weak = weak.into();
- }
- if let Some(strong) = background.strong {
- extended.background.strong = strong.into();
- }
- }
-
- if let Some(primary) = extended_palette.primary {
- if let Some(base) = primary.base {
- extended.primary.base = base.into();
- }
- if let Some(weak) = primary.weak {
- extended.primary.weak = weak.into();
- }
- if let Some(strong) = primary.strong {
- extended.primary.strong = strong.into();
- }
- }
-
- if let Some(secondary) = extended_palette.secondary {
- if let Some(base) = secondary.base {
- extended.secondary.base = base.into();
- }
- if let Some(weak) = secondary.weak {
- extended.secondary.weak = weak.into();
- }
- if let Some(strong) = secondary.strong {
- extended.secondary.strong = strong.into();
- }
- }
-
- if let Some(success) = extended_palette.success {
- if let Some(base) = success.base {
- extended.success.base = base.into();
- }
- if let Some(weak) = success.weak {
- extended.success.weak = weak.into();
- }
- if let Some(strong) = success.strong {
- extended.success.strong = strong.into();
- }
- }
-
- if let Some(danger) = extended_palette.danger {
- if let Some(base) = danger.base {
- extended.danger.base = base.into();
- }
- if let Some(weak) = danger.weak {
- extended.danger.weak = weak.into();
- }
- if let Some(strong) = danger.strong {
- extended.danger.strong = strong.into();
- }
- }
-
- if let Some(warning) = extended_palette.warning {
- if let Some(base) = warning.base {
- extended.warning.base = base.into();
- }
- if let Some(weak) = warning.weak {
- extended.warning.weak = weak.into();
- }
- if let Some(strong) = warning.strong {
- extended.warning.strong = strong.into();
- }
- }
- }
-
- extended
- }
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ExtendedThemePalette {
- background: Option<ThemeBackground>,
- primary: Option<ThemePrimary>,
- secondary: Option<ThemeSecondary>,
- success: Option<ThemeSuccess>,
- danger: Option<ThemeDanger>,
- warning: Option<ThemeWarning>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemeBackground {
- base: Option<ThemePair>,
- weak: Option<ThemePair>,
- strong: Option<ThemePair>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemePrimary {
- base: Option<ThemePair>,
- weak: Option<ThemePair>,
- strong: Option<ThemePair>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemeSecondary {
- base: Option<ThemePair>,
- weak: Option<ThemePair>,
- strong: Option<ThemePair>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemeSuccess {
- base: Option<ThemePair>,
- weak: Option<ThemePair>,
- strong: Option<ThemePair>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemeDanger {
- base: Option<ThemePair>,
- weak: Option<ThemePair>,
- strong: Option<ThemePair>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemeWarning {
- base: Option<ThemePair>,
- weak: Option<ThemePair>,
- strong: Option<ThemePair>,
-}
-
-#[derive(Debug, Default, serde::Deserialize)]
-struct ThemePair {
- #[serde(with = "color_serde")]
- color: Color,
- #[serde(with = "color_serde")]
- text: Color,
-}
-
-impl From<ThemePair> for iced::theme::palette::Pair {
- fn from(pair: ThemePair) -> Self {
- Self {
- color: pair.color,
- text: pair.text,
- }
- }
-}
-
-mod color_serde {
- use iced::Color;
- use serde::{Deserialize, Deserializer};
-
- pub fn deserialize<'de, D>(deserializer: D) -> Result<Color, D::Error>
- where
- D: Deserializer<'de>,
- {
- Ok(String::deserialize(deserializer)
- .map(|hex| Color::parse(&hex))?
- .unwrap_or(Color::TRANSPARENT))
- }
-}
diff --git a/src/types.rs b/src/types.rs
index a7fae1c..608f285 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -8,14 +8,20 @@ pub use element_name::ElementName;
use iced::advanced::widget::Id;
use iced::widget::{pane_grid, text_editor};
use iced_anim::Event;
+use material_theme::Theme;
pub use project::Project;
pub use rendered_element::*;
use crate::Error;
+use crate::config::Config;
+pub type Element<'a, Message> = iced::Element<'a, Message, Theme>;
+
+#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum Message {
- SwitchTheme(Event<iced::Theme>),
+ ConfigLoad(Result<Config, Error>),
+ SwitchTheme(Event<Theme>),
CopyCode,
SwitchPage(DesignerPane),
EditorAction(text_editor::Action),
diff --git a/src/types/project.rs b/src/types/project.rs
index 721783e..11789ac 100644
--- a/src/types/project.rs
+++ b/src/types/project.rs
@@ -1,24 +1,19 @@
use std::path::{Path, PathBuf};
-use std::sync::Arc;
extern crate fxhash;
-use fxhash::FxHashMap;
use iced::Theme;
use rust_format::{Edition, Formatter, RustFmt};
use serde::{Deserialize, Serialize};
use super::rendered_element::RenderedElement;
use crate::Error;
-use crate::config::Config;
-use crate::theme::{theme_from_str, theme_index, theme_to_string};
+use crate::appearance::iced_theme_from_str;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
pub title: Option<String>,
pub theme: Option<String>,
pub element_tree: Option<RenderedElement>,
- #[serde(skip)]
- theme_cache: FxHashMap<String, String>,
}
impl Default for Project {
@@ -33,45 +28,24 @@ impl Project {
title: None,
theme: None,
element_tree: None,
- theme_cache: FxHashMap::default(),
}
}
- pub fn get_theme(&self, config: &Config) -> Theme {
+ pub fn get_theme(&self) -> Theme {
match &self.theme {
- Some(theme) => theme_from_str(Some(config), theme),
- None => Theme::default(),
+ Some(theme) => iced_theme_from_str(theme),
+ None => iced::Theme::default(),
}
}
- fn theme_code(&mut self, theme: &Theme) -> String {
- let theme_name = theme.to_string();
- if theme_index(&theme_name, Theme::ALL).is_none() {
- (*self
- .theme_cache
- .entry(theme_name)
- .or_insert(theme_to_string(theme)))
- .to_string()
- } else {
- theme_name.replace(" ", "")
- }
- }
-
- pub async fn from_path(
- path: PathBuf,
- config: Arc<Config>,
- ) -> Result<(PathBuf, Self), Error> {
+ pub async fn from_path(path: PathBuf) -> Result<(PathBuf, Self), Error> {
let contents = tokio::fs::read_to_string(&path).await?;
- let mut project: Self = serde_json::from_str(&contents)?;
-
- let _ = project.theme_code(&project.get_theme(&config));
+ let project: Self = serde_json::from_str(&contents)?;
Ok((path, project))
}
- pub async fn from_file(
- config: Arc<Config>,
- ) -> Result<(PathBuf, Self), Error> {
+ pub async fn from_file() -> Result<(PathBuf, Self), Error> {
let picked_file = rfd::AsyncFileDialog::new()
.set_title("Open a JSON file...")
.add_filter("*.json, *.JSON", &["json", "JSON"])
@@ -81,7 +55,7 @@ impl Project {
let path = picked_file.path().to_owned();
- Self::from_path(path, config).await
+ Self::from_path(path).await
}
pub async fn write_to_file(
@@ -108,28 +82,21 @@ impl Project {
Ok(path)
}
- pub fn app_code(&mut self, config: &Config) -> Result<String, Error> {
- match self.element_tree {
+ pub fn app_code(&mut self) -> Result<String, Error> {
+ use iced::debug;
+ let codegen = debug::time("Code Generation");
+
+ let result = match self.element_tree {
Some(ref element_tree) => {
let (imports, view) = element_tree.codegen();
- let theme = self.get_theme(config);
- let theme_code = self.theme_code(&theme);
- let mut theme_imports = "";
- if theme_index(&theme.to_string(), Theme::ALL).is_none() {
- if theme_code.contains("Extended") {
- theme_imports = "use iced::{{color,theme::{{Palette,palette::{{Extended,Background,Primary,Secondary,Success,Danger,Warning,Pair}}}}}};\n";
- } else {
- theme_imports = "use iced::{{color,theme::Palette}};\n";
- }
- }
+ let theme = self.get_theme();
let app_code = format!(
r#"// Automatically generated by iced Builder
use iced::{{widget::{{{imports}}},Element}};
-{theme_imports}
fn main() -> iced::Result {{
- iced::application("{}", State::update, State::view).theme(State::theme).run()
+ iced::application(State::default, State::update, State::view).title("{}").theme(State::theme).run()
}}
#[derive(Default)]
@@ -153,7 +120,7 @@ impl State {{
Some(ref t) => t,
None => "New app",
},
- theme_code
+ theme.to_string().replace(" ", "")
);
let config = rust_format::Config::new_str()
.edition(Edition::Rust2021)
@@ -163,6 +130,9 @@ impl State {{
Ok(rustfmt.format_str(app_code)?)
}
None => Err("No element tree present".into()),
- }
+ };
+
+ codegen.finish();
+ result
}
}
diff --git a/src/types/rendered_element.rs b/src/types/rendered_element.rs
index 0a78dcd..020aa46 100755
--- a/src/types/rendered_element.rs
+++ b/src/types/rendered_element.rs
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use iced::advanced::widget::Id;
-use iced::{Element, Length, widget};
+use iced::{Element, widget};
use serde::{Deserialize, Serialize};
use crate::Error;
@@ -171,34 +171,6 @@ impl RenderedElement {
self
}
- pub fn text_view<'a>(self) -> Element<'a, Message> {
- let mut children = widget::column![];
-
- if let Some(els) = self.child_elements.clone() {
- for el in els {
- children = children.push(el.clone().text_view());
- }
- }
- iced_drop::droppable(
- widget::container(
- widget::column![
- widget::text(self.name.clone().to_string()),
- children
- ]
- .width(Length::Fill)
- .spacing(10),
- )
- .padding(10)
- .style(widget::container::bordered_box),
- )
- .id(self.id().clone())
- .drag_hide(true)
- .on_drop(move |point, rect| {
- Message::MoveElement(self.clone(), point, rect)
- })
- .into()
- }
-
pub fn codegen(&self) -> (String, String) {
let mut imports = String::new();
let mut view = String::new();
@@ -223,7 +195,14 @@ impl RenderedElement {
match &self.name {
ElementName::Container => {
imports = format!("{imports}container,");
- view = format!("{view}\ncontainer({elements}){options}");
+ view = format!(
+ "{view}\ncontainer({}){options}",
+ if elements.is_empty() {
+ String::from("\"\"")
+ } else {
+ elements.to_string()
+ }
+ );
}
ElementName::Row => {
imports = format!("{imports}row,");
@@ -237,7 +216,7 @@ impl RenderedElement {
imports = format!("{imports}text,");
view = format!(
"{view}\ntext(\"{}\"){options}",
- if *string == String::new() {
+ if string.is_empty() {
"New Text"
} else {
string
@@ -248,7 +227,7 @@ impl RenderedElement {
imports = format!("{imports}button,");
view = format!(
"{view}\nbutton(\"{}\"){options}",
- if *string == String::new() {
+ if string.is_empty() {
"New Button"
} else {
string
@@ -317,53 +296,98 @@ impl<'a> From<RenderedElement> for Element<'a, Message> {
let child_elements = copy.child_elements.unwrap_or_default();
let content: Element<'a, Message> = match copy.name {
- ElementName::Text(s) => {
- if s.is_empty() {
- widget::text("New Text").apply_options(copy.options).into()
- } else {
- widget::text(s).apply_options(copy.options).into()
- }
- }
- ElementName::Button(s) => {
- if s.is_empty() {
- widget::button(widget::text("New Button"))
- .apply_options(copy.options)
- .into()
- } else {
- widget::button(widget::text(s))
- .apply_options(copy.options)
- .into()
- }
+ ElementName::Text(s) => if s.is_empty() {
+ widget::text("New Text")
+ } else {
+ widget::text(s)
}
+ .apply_options(copy.options)
+ .into(),
+ ElementName::Button(s) => widget::button(if s.is_empty() {
+ widget::text("New Button")
+ } else {
+ widget::text(s)
+ })
+ .apply_options(copy.options)
+ .into(),
ElementName::Svg(p) => {
widget::svg(p).apply_options(copy.options).into()
}
ElementName::Image(p) => {
widget::image(p).apply_options(copy.options).into()
}
- ElementName::Container => {
- widget::container(if child_elements.len() == 1 {
- child_elements[0].clone().into()
- } else {
- Element::from("")
- })
- .apply_options(copy.options)
- .padding(20)
- .into()
+ ElementName::Container => if child_elements.len() == 1 {
+ widget::container(child_elements[0].clone())
+ } else {
+ widget::container("New Container").style(
+ |theme: &iced::Theme| widget::container::Style {
+ border: iced::Border {
+ color: theme.palette().text,
+
+ width: 2.0,
+ radius: 4.into(),
+ },
+ ..Default::default()
+ },
+ )
}
- ElementName::Row => widget::Row::from_vec(
- child_elements.into_iter().map(Into::into).collect(),
- )
- .padding(20)
- .apply_options(copy.options)
- .into(),
- ElementName::Column => widget::Column::from_vec(
- child_elements.into_iter().map(Into::into).collect(),
- )
.padding(20)
.apply_options(copy.options)
.into(),
+ ElementName::Row => {
+ if !child_elements.is_empty() {
+ widget::Row::with_children(
+ child_elements.into_iter().map(Into::into),
+ )
+ .padding(20)
+ .apply_options(copy.options)
+ .into()
+ } else {
+ widget::container(
+ widget::row!["New Row"]
+ .padding(20)
+ .apply_options(copy.options),
+ )
+ .style(|theme: &iced::Theme| widget::container::Style {
+ border: iced::Border {
+ color: theme.palette().text,
+
+ width: 2.0,
+ radius: 4.into(),
+ },
+ ..Default::default()
+ })
+ .into()
+ }
+ }
+ ElementName::Column => {
+ if !child_elements.is_empty() {
+ widget::Column::with_children(
+ child_elements.into_iter().map(Into::into),
+ )
+ .padding(20)
+ .apply_options(copy.options)
+ .into()
+ } else {
+ widget::container(
+ widget::column!["New Column"]
+ .padding(20)
+ .apply_options(copy.options),
+ )
+ .style(|theme: &iced::Theme| widget::container::Style {
+ border: iced::Border {
+ color: theme.palette().text,
+
+ width: 2.0,
+ radius: 4.into(),
+ },
+ ..Default::default()
+ })
+ .into()
+ }
+ }
};
+
iced_drop::droppable(content)
.id(value.id().clone())
.drag_hide(true)
diff --git a/src/widget.rs b/src/widget.rs
index f1eb0f3..859d25e 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -1,5 +1,7 @@
-use iced::Element;
-use iced::widget::{container, text, tooltip};
+use iced::widget::{self, container, text, tooltip};
+use material_theme::Theme;
+
+use crate::types::Element;
pub mod tip {
pub use super::tooltip::Position;
@@ -12,10 +14,16 @@ pub fn tip<'a, Message: 'a>(
) -> Element<'a, Message> {
tooltip(
target,
- container(text(tip).size(14))
- .padding(5)
- .style(container::rounded_box),
+ container(text(tip).size(14)).padding(5).style(|theme| {
+ let base = material_theme::container::surface_container_low(theme);
+ container::Style {
+ border: iced::border::rounded(4),
+ ..base
+ }
+ }),
position,
)
.into()
}
+
+pub type Text<'a> = widget::Text<'a, Theme>;