summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml6
-rw-r--r--core/src/padding.rs2
-rw-r--r--core/src/renderer/null.rs1
-rw-r--r--core/src/text.rs21
-rw-r--r--core/src/text/editor.rs3
-rw-r--r--core/src/text/paragraph.rs1
-rw-r--r--core/src/widget/operation.rs4
-rw-r--r--core/src/widget/operation/focusable.rs14
-rw-r--r--core/src/widget/text.rs15
-rw-r--r--core/src/window/settings/windows.rs7
-rw-r--r--examples/component/Cargo.toml10
-rw-r--r--examples/component/src/main.rs149
-rw-r--r--examples/editor/src/main.rs20
-rw-r--r--examples/markdown/src/main.rs18
-rw-r--r--examples/modal/src/main.rs28
-rw-r--r--examples/pane_grid/src/main.rs22
-rw-r--r--examples/styling/src/main.rs2
-rw-r--r--examples/todos/src/main.rs3
-rw-r--r--examples/tour/src/main.rs2
-rw-r--r--graphics/src/text.rs12
-rw-r--r--graphics/src/text/editor.rs29
-rw-r--r--graphics/src/text/paragraph.rs7
-rw-r--r--runtime/src/window.rs38
-rw-r--r--wgpu/src/lib.rs1
-rw-r--r--widget/src/checkbox.rs12
-rw-r--r--widget/src/container.rs12
-rw-r--r--widget/src/helpers.rs7
-rw-r--r--widget/src/lazy.rs2
-rw-r--r--widget/src/lazy/component.rs7
-rw-r--r--widget/src/lazy/helpers.rs12
-rw-r--r--widget/src/lazy/responsive.rs1
-rw-r--r--widget/src/lib.rs3
-rw-r--r--widget/src/markdown.rs275
-rw-r--r--widget/src/overlay/menu.rs1
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/pane_grid/controls.rs59
-rw-r--r--widget/src/pane_grid/title_bar.rs317
-rw-r--r--widget/src/pick_list.rs5
-rw-r--r--widget/src/radio.rs13
-rw-r--r--widget/src/stack.rs91
-rw-r--r--widget/src/text/rich.rs49
-rw-r--r--widget/src/text_editor.rs13
-rw-r--r--widget/src/text_input.rs129
-rw-r--r--widget/src/toggler.rs19
-rw-r--r--winit/src/clipboard.rs10
-rw-r--r--winit/src/conversion.rs4
-rw-r--r--winit/src/program.rs96
-rw-r--r--winit/src/program/window_manager.rs8
48 files changed, 1096 insertions, 466 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 65c5007e..52464e38 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,7 +27,9 @@ wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enable the `tiny-skia` software renderer backend
tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget
-image = ["iced_widget/image", "dep:image"]
+image = ["image-without-codecs", "image/default"]
+# Enables the `Image` widget, without any built-in codecs of the `image` crate
+image-without-codecs = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget
svg = ["iced_widget/svg"]
# Enables the `Canvas` widget
@@ -147,7 +149,7 @@ glam = "0.25"
glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "feef9f5630c2adb3528937e55f7bfad2da561a65" }
guillotiere = "0.6"
half = "2.2"
-image = "0.24"
+image = { version = "0.24", default-features = false }
kamadak-exif = "0.5"
kurbo = "0.10"
log = "0.4"
diff --git a/core/src/padding.rs b/core/src/padding.rs
index fdaa0236..e26cdd9b 100644
--- a/core/src/padding.rs
+++ b/core/src/padding.rs
@@ -32,7 +32,7 @@ use crate::{Pixels, Size};
/// let widget = Widget::new().padding(20); // 20px on all sides
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
/// ```
-#[derive(Debug, Copy, Clone, Default)]
+#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct Padding {
/// Top padding
pub top: f32,
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index e3a07280..bbcdd8ff 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -161,6 +161,7 @@ impl text::Editor for () {
_new_font: Self::Font,
_new_size: Pixels,
_new_line_height: text::LineHeight,
+ _new_wrapping: text::Wrapping,
_new_highlighter: &mut impl text::Highlighter,
) {
}
diff --git a/core/src/text.rs b/core/src/text.rs
index 436fee9a..d7b7fee4 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -41,6 +41,9 @@ pub struct Text<Content = String, Font = crate::Font> {
/// The [`Shaping`] strategy of the [`Text`].
pub shaping: Shaping,
+
+ /// The [`Wrapping`] strategy of the [`Text`].
+ pub wrapping: Wrapping,
}
/// The shaping strategy of some text.
@@ -67,6 +70,22 @@ pub enum Shaping {
Advanced,
}
+/// The wrapping strategy of some text.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
+pub enum Wrapping {
+ /// No wrapping.
+ None,
+ /// Wraps at the word level.
+ ///
+ /// This is the default.
+ #[default]
+ Word,
+ /// Wraps at the glyph level.
+ Glyph,
+ /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself.
+ WordOrGlyph,
+}
+
/// The height of a line of text in a paragraph.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineHeight {
@@ -252,7 +271,7 @@ pub struct Span<'a, Link = (), Font = crate::Font> {
}
/// A text highlight.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Highlight {
/// The [`Background`] of the highlight.
pub background: Background,
diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs
index 135707d1..cd30db3a 100644
--- a/core/src/text/editor.rs
+++ b/core/src/text/editor.rs
@@ -1,6 +1,6 @@
//! Edit text.
use crate::text::highlighter::{self, Highlighter};
-use crate::text::LineHeight;
+use crate::text::{LineHeight, Wrapping};
use crate::{Pixels, Point, Rectangle, Size};
use std::sync::Arc;
@@ -50,6 +50,7 @@ pub trait Editor: Sized + Default {
new_font: Self::Font,
new_size: Pixels,
new_line_height: LineHeight,
+ new_wrapping: Wrapping,
new_highlighter: &mut impl Highlighter,
);
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
index 04a97f35..924276c3 100644
--- a/core/src/text/paragraph.rs
+++ b/core/src/text/paragraph.rs
@@ -95,6 +95,7 @@ impl<P: Paragraph> Plain<P> {
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
+ wrapping: text.wrapping,
}) {
Difference::None => {}
Difference::Bounds => {
diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs
index 741e1a5f..4ee4b4a7 100644
--- a/core/src/widget/operation.rs
+++ b/core/src/widget/operation.rs
@@ -295,7 +295,7 @@ where
/// Chains the output of an [`Operation`] with the provided function to
/// build a new [`Operation`].
-pub fn chain<A, B, O>(
+pub fn then<A, B, O>(
operation: impl Operation<A> + 'static,
f: fn(A) -> O,
) -> impl Operation<B>
@@ -361,7 +361,7 @@ where
Outcome::Chain(Box::new((self.next)(value)))
}
Outcome::Chain(operation) => {
- Outcome::Chain(Box::new(chain(operation, self.next)))
+ Outcome::Chain(Box::new(then(operation, self.next)))
}
}
}
diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs
index 0a6f2e96..867c682e 100644
--- a/core/src/widget/operation/focusable.rs
+++ b/core/src/widget/operation/focusable.rs
@@ -94,7 +94,10 @@ pub fn count() -> impl Operation<Count> {
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the previous focusable widget.
/// - if not found, focuses the last focusable widget.
-pub fn focus_previous() -> impl Operation {
+pub fn focus_previous<T>() -> impl Operation<T>
+where
+ T: Send + 'static,
+{
struct FocusPrevious {
count: Count,
current: usize,
@@ -128,13 +131,16 @@ pub fn focus_previous() -> impl Operation {
}
}
- operation::chain(count(), |count| FocusPrevious { count, current: 0 })
+ operation::then(count(), |count| FocusPrevious { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the next focusable widget.
/// - if not found, focuses the first focusable widget.
-pub fn focus_next() -> impl Operation {
+pub fn focus_next<T>() -> impl Operation<T>
+where
+ T: Send + 'static,
+{
struct FocusNext {
count: Count,
current: usize,
@@ -162,7 +168,7 @@ pub fn focus_next() -> impl Operation {
}
}
- operation::chain(count(), |count| FocusNext { count, current: 0 })
+ operation::then(count(), |count| FocusNext { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index 5c5b78dd..d8d6e4c6 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -11,7 +11,7 @@ use crate::{
Widget,
};
-pub use text::{LineHeight, Shaping};
+pub use text::{LineHeight, Shaping, Wrapping};
/// A paragraph of text.
#[allow(missing_debug_implementations)]
@@ -29,6 +29,7 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
+ wrapping: Wrapping,
class: Theme::Class<'a>,
}
@@ -48,7 +49,8 @@ where
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
- shaping: Shaping::Basic,
+ shaping: Shaping::default(),
+ wrapping: Wrapping::default(),
class: Theme::default(),
}
}
@@ -115,6 +117,12 @@ where
self
}
+ /// Sets the [`Wrapping`] strategy of the [`Text`].
+ pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
+ self.wrapping = wrapping;
+ self
+ }
+
/// Sets the style of the [`Text`].
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
@@ -198,6 +206,7 @@ where
self.horizontal_alignment,
self.vertical_alignment,
self.shaping,
+ self.wrapping,
)
}
@@ -232,6 +241,7 @@ pub fn layout<Renderer>(
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
+ wrapping: Wrapping,
) -> layout::Node
where
Renderer: text::Renderer,
@@ -253,6 +263,7 @@ where
horizontal_alignment,
vertical_alignment,
shaping,
+ wrapping,
});
paragraph.min_bounds()
diff --git a/core/src/window/settings/windows.rs b/core/src/window/settings/windows.rs
index 88fe2fbd..a47582a6 100644
--- a/core/src/window/settings/windows.rs
+++ b/core/src/window/settings/windows.rs
@@ -8,6 +8,12 @@ pub struct PlatformSpecific {
/// Whether show or hide the window icon in the taskbar.
pub skip_taskbar: bool,
+
+ /// Shows or hides the background drop shadow for undecorated windows.
+ ///
+ /// The shadow is hidden by default.
+ /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
+ pub undecorated_shadow: bool,
}
impl Default for PlatformSpecific {
@@ -15,6 +21,7 @@ impl Default for PlatformSpecific {
Self {
drag_and_drop: true,
skip_taskbar: false,
+ undecorated_shadow: false,
}
}
}
diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml
deleted file mode 100644
index 83b7b8a4..00000000
--- a/examples/component/Cargo.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[package]
-name = "component"
-version = "0.1.0"
-authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
-edition = "2021"
-publish = false
-
-[dependencies]
-iced.workspace = true
-iced.features = ["debug", "lazy"]
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
deleted file mode 100644
index a5d2e508..00000000
--- a/examples/component/src/main.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-use iced::widget::center;
-use iced::Element;
-
-use numeric_input::numeric_input;
-
-pub fn main() -> iced::Result {
- iced::run("Component - Iced", Component::update, Component::view)
-}
-
-#[derive(Default)]
-struct Component {
- value: Option<u32>,
-}
-
-#[derive(Debug, Clone, Copy)]
-enum Message {
- NumericInputChanged(Option<u32>),
-}
-
-impl Component {
- fn update(&mut self, message: Message) {
- match message {
- Message::NumericInputChanged(value) => {
- self.value = value;
- }
- }
- }
-
- fn view(&self) -> Element<Message> {
- center(numeric_input(self.value, Message::NumericInputChanged))
- .padding(20)
- .into()
- }
-}
-
-mod numeric_input {
- use iced::widget::{button, component, row, text, text_input, Component};
- use iced::{Center, Element, Fill, Length, Size};
-
- pub struct NumericInput<Message> {
- value: Option<u32>,
- on_change: Box<dyn Fn(Option<u32>) -> Message>,
- }
-
- pub fn numeric_input<Message>(
- value: Option<u32>,
- on_change: impl Fn(Option<u32>) -> Message + 'static,
- ) -> NumericInput<Message> {
- NumericInput::new(value, on_change)
- }
-
- #[derive(Debug, Clone)]
- pub enum Event {
- InputChanged(String),
- IncrementPressed,
- DecrementPressed,
- }
-
- impl<Message> NumericInput<Message> {
- pub fn new(
- value: Option<u32>,
- on_change: impl Fn(Option<u32>) -> Message + 'static,
- ) -> Self {
- Self {
- value,
- on_change: Box::new(on_change),
- }
- }
- }
-
- impl<Message, Theme> Component<Message, Theme> for NumericInput<Message>
- where
- Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
- {
- type State = ();
- type Event = Event;
-
- fn update(
- &mut self,
- _state: &mut Self::State,
- event: Event,
- ) -> Option<Message> {
- match event {
- Event::IncrementPressed => Some((self.on_change)(Some(
- self.value.unwrap_or_default().saturating_add(1),
- ))),
- Event::DecrementPressed => Some((self.on_change)(Some(
- self.value.unwrap_or_default().saturating_sub(1),
- ))),
- Event::InputChanged(value) => {
- if value.is_empty() {
- Some((self.on_change)(None))
- } else {
- value
- .parse()
- .ok()
- .map(Some)
- .map(self.on_change.as_ref())
- }
- }
- }
- }
-
- fn view(&self, _state: &Self::State) -> Element<'_, Event, Theme> {
- let button = |label, on_press| {
- button(text(label).width(Fill).height(Fill).center())
- .width(40)
- .height(40)
- .on_press(on_press)
- };
-
- row![
- button("-", Event::DecrementPressed),
- text_input(
- "Type a number",
- self.value
- .as_ref()
- .map(u32::to_string)
- .as_deref()
- .unwrap_or(""),
- )
- .on_input(Event::InputChanged)
- .padding(10),
- button("+", Event::IncrementPressed),
- ]
- .align_y(Center)
- .spacing(10)
- .into()
- }
-
- fn size_hint(&self) -> Size<Length> {
- Size {
- width: Length::Fill,
- height: Length::Shrink,
- }
- }
- }
-
- impl<'a, Message, Theme> From<NumericInput<Message>>
- for Element<'a, Message, Theme>
- where
- Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
- Message: 'a,
- {
- fn from(numeric_input: NumericInput<Message>) -> Self {
- component(numeric_input)
- }
- }
-}
diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs
index aa07b328..5f12aec5 100644
--- a/examples/editor/src/main.rs
+++ b/examples/editor/src/main.rs
@@ -2,7 +2,7 @@ use iced::highlighter;
use iced::keyboard;
use iced::widget::{
self, button, column, container, horizontal_space, pick_list, row, text,
- text_editor, tooltip,
+ text_editor, toggler, tooltip,
};
use iced::{Center, Element, Fill, Font, Subscription, Task, Theme};
@@ -24,6 +24,7 @@ struct Editor {
file: Option<PathBuf>,
content: text_editor::Content,
theme: highlighter::Theme,
+ word_wrap: bool,
is_loading: bool,
is_dirty: bool,
}
@@ -32,6 +33,7 @@ struct Editor {
enum Message {
ActionPerformed(text_editor::Action),
ThemeSelected(highlighter::Theme),
+ WordWrapToggled(bool),
NewFile,
OpenFile,
FileOpened(Result<(PathBuf, Arc<String>), Error>),
@@ -46,6 +48,7 @@ impl Editor {
file: None,
content: text_editor::Content::new(),
theme: highlighter::Theme::SolarizedDark,
+ word_wrap: true,
is_loading: true,
is_dirty: false,
},
@@ -76,6 +79,11 @@ impl Editor {
Task::none()
}
+ Message::WordWrapToggled(word_wrap) => {
+ self.word_wrap = word_wrap;
+
+ Task::none()
+ }
Message::NewFile => {
if !self.is_loading {
self.file = None;
@@ -152,6 +160,11 @@ impl Editor {
self.is_dirty.then_some(Message::SaveFile)
),
horizontal_space(),
+ toggler(
+ Some("Word Wrap"),
+ self.word_wrap,
+ Message::WordWrapToggled
+ ),
pick_list(
highlighter::Theme::ALL,
Some(self.theme),
@@ -189,6 +202,11 @@ impl Editor {
text_editor(&self.content)
.height(Fill)
.on_action(Message::ActionPerformed)
+ .wrapping(if self.word_wrap {
+ text::Wrapping::Word
+ } else {
+ text::Wrapping::None
+ })
.highlight(
self.file
.as_deref()
diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs
index eb51f985..5605478f 100644
--- a/examples/markdown/src/main.rs
+++ b/examples/markdown/src/main.rs
@@ -29,8 +29,7 @@ impl Markdown {
(
Self {
content: text_editor::Content::with_text(INITIAL_CONTENT),
- items: markdown::parse(INITIAL_CONTENT, theme.palette())
- .collect(),
+ items: markdown::parse(INITIAL_CONTENT).collect(),
theme,
},
widget::focus_next(),
@@ -45,11 +44,8 @@ impl Markdown {
self.content.perform(action);
if is_edit {
- self.items = markdown::parse(
- &self.content.text(),
- self.theme.palette(),
- )
- .collect();
+ self.items =
+ markdown::parse(&self.content.text()).collect();
}
}
Message::LinkClicked(link) => {
@@ -67,8 +63,12 @@ impl Markdown {
.font(Font::MONOSPACE)
.highlight("markdown", highlighter::Theme::Base16Ocean);
- let preview = markdown(&self.items, markdown::Settings::default())
- .map(Message::LinkClicked);
+ let preview = markdown(
+ &self.items,
+ markdown::Settings::default(),
+ markdown::Style::from_palette(self.theme.palette()),
+ )
+ .map(Message::LinkClicked);
row![editor, scrollable(preview).spacing(10).height(Fill)]
.spacing(10)
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index f1f0e8ad..067ca24d 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -201,19 +201,21 @@ where
{
stack![
base.into(),
- mouse_area(center(opaque(content)).style(|_theme| {
- container::Style {
- background: Some(
- Color {
- a: 0.8,
- ..Color::BLACK
- }
- .into(),
- ),
- ..container::Style::default()
- }
- }))
- .on_press(on_blur)
+ opaque(
+ mouse_area(center(opaque(content)).style(|_theme| {
+ container::Style {
+ background: Some(
+ Color {
+ a: 0.8,
+ ..Color::BLACK
+ }
+ .into(),
+ ),
+ ..container::Style::default()
+ }
+ }))
+ .on_press(on_blur)
+ )
]
.into()
}
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index f18fc5f3..67f4d27f 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -154,11 +154,23 @@ impl Example {
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
- .controls(view_controls(
- id,
- total_panes,
- pane.is_pinned,
- is_maximized,
+ .controls(pane_grid::Controls::dynamic(
+ view_controls(
+ id,
+ total_panes,
+ pane.is_pinned,
+ is_maximized,
+ ),
+ button(text("X").size(14))
+ .style(button::danger)
+ .padding(3)
+ .on_press_maybe(
+ if total_panes > 1 && !pane.is_pinned {
+ Some(Message::Close(id))
+ } else {
+ None
+ },
+ ),
))
.padding(10)
.style(if is_focused {
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index 527aaa29..e19d5cf7 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -78,7 +78,7 @@ impl Styling {
.on_toggle(Message::CheckboxToggled);
let toggler = toggler(
- String::from("Toggle me!"),
+ Some("Toggle me!"),
self.toggler_value,
Message::TogglerToggled,
)
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 86845f87..a5f7b36a 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -202,7 +202,8 @@ impl Todos {
.on_input(Message::InputChanged)
.on_submit(Message::CreateTask)
.padding(15)
- .size(30);
+ .size(30)
+ .align_x(Center);
let controls = view_controls(tasks, *filter);
let filtered_tasks =
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index ee4754e6..0dd588fe 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -358,7 +358,7 @@ impl Tour {
.push("A toggler is mostly used to enable or disable something.")
.push(
Container::new(toggler(
- "Toggle me to continue...".to_owned(),
+ Some("Toggle me to continue..."),
self.toggler,
Message::TogglerChanged,
))
diff --git a/graphics/src/text.rs b/graphics/src/text.rs
index 23ec14d4..feb9932a 100644
--- a/graphics/src/text.rs
+++ b/graphics/src/text.rs
@@ -11,7 +11,7 @@ pub use cosmic_text;
use crate::core::alignment;
use crate::core::font::{self, Font};
-use crate::core::text::Shaping;
+use crate::core::text::{Shaping, Wrapping};
use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};
use once_cell::sync::OnceCell;
@@ -306,6 +306,16 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
}
}
+/// Converts some [`Wrapping`] strategy to a [`cosmic_text::Wrap`] strategy.
+pub fn to_wrap(wrapping: Wrapping) -> cosmic_text::Wrap {
+ match wrapping {
+ Wrapping::None => cosmic_text::Wrap::None,
+ Wrapping::Word => cosmic_text::Wrap::Word,
+ Wrapping::Glyph => cosmic_text::Wrap::Glyph,
+ Wrapping::WordOrGlyph => cosmic_text::Wrap::WordOrGlyph,
+ }
+}
+
/// Converts some [`Color`] to a [`cosmic_text::Color`].
pub fn to_color(color: Color) -> cosmic_text::Color {
let [r, g, b, a] = color.into_rgba8();
diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs
index 80733bbb..1f1d0050 100644
--- a/graphics/src/text/editor.rs
+++ b/graphics/src/text/editor.rs
@@ -3,7 +3,7 @@ use crate::core::text::editor::{
self, Action, Cursor, Direction, Edit, Motion,
};
use crate::core::text::highlighter::{self, Highlighter};
-use crate::core::text::LineHeight;
+use crate::core::text::{LineHeight, Wrapping};
use crate::core::{Font, Pixels, Point, Rectangle, Size};
use crate::text;
@@ -437,6 +437,7 @@ impl editor::Editor for Editor {
new_font: Font,
new_size: Pixels,
new_line_height: LineHeight,
+ new_wrapping: Wrapping,
new_highlighter: &mut impl Highlighter,
) {
let editor =
@@ -448,13 +449,12 @@ impl editor::Editor for Editor {
let mut font_system =
text::font_system().write().expect("Write font system");
+ let buffer = buffer_mut_from_editor(&mut internal.editor);
+
if font_system.version() != internal.version {
log::trace!("Updating `FontSystem` of `Editor`...");
- for line in buffer_mut_from_editor(&mut internal.editor)
- .lines
- .iter_mut()
- {
+ for line in buffer.lines.iter_mut() {
line.reset();
}
@@ -465,10 +465,7 @@ impl editor::Editor for Editor {
if new_font != internal.font {
log::trace!("Updating font of `Editor`...");
- for line in buffer_mut_from_editor(&mut internal.editor)
- .lines
- .iter_mut()
- {
+ for line in buffer.lines.iter_mut() {
let _ = line.set_attrs_list(cosmic_text::AttrsList::new(
text::to_attributes(new_font),
));
@@ -478,7 +475,7 @@ impl editor::Editor for Editor {
internal.topmost_line_changed = Some(0);
}
- let metrics = buffer_from_editor(&internal.editor).metrics();
+ let metrics = buffer.metrics();
let new_line_height = new_line_height.to_absolute(new_size);
if new_size.0 != metrics.font_size
@@ -486,16 +483,24 @@ impl editor::Editor for Editor {
{
log::trace!("Updating `Metrics` of `Editor`...");
- buffer_mut_from_editor(&mut internal.editor).set_metrics(
+ buffer.set_metrics(
font_system.raw(),
cosmic_text::Metrics::new(new_size.0, new_line_height.0),
);
}
+ let new_wrap = text::to_wrap(new_wrapping);
+
+ if new_wrap != buffer.wrap() {
+ log::trace!("Updating `Wrap` strategy of `Editor`...");
+
+ buffer.set_wrap(font_system.raw(), new_wrap);
+ }
+
if new_bounds != internal.bounds {
log::trace!("Updating size of `Editor`...");
- buffer_mut_from_editor(&mut internal.editor).set_size(
+ buffer.set_size(
font_system.raw(),
Some(new_bounds.width),
Some(new_bounds.height),
diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs
index b9f9c833..07ddbb82 100644
--- a/graphics/src/text/paragraph.rs
+++ b/graphics/src/text/paragraph.rs
@@ -1,7 +1,7 @@
//! Draw paragraphs.
use crate::core;
use crate::core::alignment;
-use crate::core::text::{Hit, Shaping, Span, Text};
+use crate::core::text::{Hit, Shaping, Span, Text, Wrapping};
use crate::core::{Font, Point, Rectangle, Size};
use crate::text;
@@ -17,6 +17,7 @@ struct Internal {
buffer: cosmic_text::Buffer,
font: Font,
shaping: Shaping,
+ wrapping: Wrapping,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
bounds: Size,
@@ -94,6 +95,7 @@ impl core::text::Paragraph for Paragraph {
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
+ wrapping: text.wrapping,
bounds: text.bounds,
min_bounds,
version: font_system.version(),
@@ -160,6 +162,7 @@ impl core::text::Paragraph for Paragraph {
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
+ wrapping: text.wrapping,
bounds: text.bounds,
min_bounds,
version: font_system.version(),
@@ -192,6 +195,7 @@ impl core::text::Paragraph for Paragraph {
|| metrics.line_height != text.line_height.to_absolute(text.size).0
|| paragraph.font != text.font
|| paragraph.shaping != text.shaping
+ || paragraph.wrapping != text.wrapping
|| paragraph.horizontal_alignment != text.horizontal_alignment
|| paragraph.vertical_alignment != text.vertical_alignment
{
@@ -387,6 +391,7 @@ impl Default for Internal {
}),
font: Font::default(),
shaping: Shaping::default(),
+ wrapping: Wrapping::default(),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
bounds: Size::ZERO,
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index cd27cdfe..cdf3d80a 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -63,6 +63,9 @@ pub enum Action {
/// Get the current logical coordinates of the window.
GetPosition(Id, oneshot::Sender<Option<Point>>),
+ /// Get the current scale factor (DPI) of the window.
+ GetScaleFactor(Id, oneshot::Sender<f32>),
+
/// Move the window to the given logical coordinates.
///
/// Unsupported on Wayland.
@@ -144,6 +147,18 @@ pub enum Action {
/// Screenshot the viewport of the window.
Screenshot(Id, oneshot::Sender<Screenshot>),
+
+ /// Enables mouse passthrough for the given window.
+ ///
+ /// This disables mouse events for the window and passes mouse events
+ /// through to whatever window is underneath.
+ EnableMousePassthrough(Id),
+
+ /// Disable mouse passthrough for the given window.
+ ///
+ /// This enables mouse events for the window and stops mouse events
+ /// from being passed to whatever is underneath.
+ DisableMousePassthrough(Id),
}
/// Subscribes to the frames of the window of the running application.
@@ -292,6 +307,13 @@ pub fn get_position(id: Id) -> Task<Option<Point>> {
})
}
+/// Gets the scale factor of the window with the given [`Id`].
+pub fn get_scale_factor(id: Id) -> Task<f32> {
+ task::oneshot(move |channel| {
+ crate::Action::Window(Action::GetScaleFactor(id, channel))
+ })
+}
+
/// Moves the window to the given logical coordinates.
pub fn move_to<T>(id: Id, position: Point) -> Task<T> {
task::effect(crate::Action::Window(Action::Move(id, position)))
@@ -396,3 +418,19 @@ pub fn screenshot(id: Id) -> Task<Screenshot> {
crate::Action::Window(Action::Screenshot(id, channel))
})
}
+
+/// Enables mouse passthrough for the given window.
+///
+/// This disables mouse events for the window and passes mouse events
+/// through to whatever window is underneath.
+pub fn enable_mouse_passthrough<Message>(id: Id) -> Task<Message> {
+ task::effect(crate::Action::Window(Action::EnableMousePassthrough(id)))
+}
+
+/// Disable mouse passthrough for the given window.
+///
+/// This enables mouse events for the window and stops mouse events
+/// from being passed to whatever is underneath.
+pub fn disable_mouse_passthrough<Message>(id: Id) -> Task<Message> {
+ task::effect(crate::Action::Window(Action::DisableMousePassthrough(id)))
+}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 39167514..d79f0dc8 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -408,6 +408,7 @@ impl Renderer {
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: core::text::Shaping::Basic,
+ wrapping: core::text::Wrapping::Word,
};
renderer.fill_text(
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index e5abfbb4..32db5090 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -50,6 +50,7 @@ pub struct Checkbox<
text_size: Option<Pixels>,
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
+ text_wrapping: text::Wrapping,
font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
class: Theme::Class<'a>,
@@ -81,7 +82,8 @@ where
spacing: Self::DEFAULT_SPACING,
text_size: None,
text_line_height: text::LineHeight::default(),
- text_shaping: text::Shaping::Basic,
+ text_shaping: text::Shaping::default(),
+ text_wrapping: text::Wrapping::default(),
font: None,
icon: Icon {
font: Renderer::ICON_FONT,
@@ -158,6 +160,12 @@ where
self
}
+ /// Sets the [`text::Wrapping`] strategy of the [`Checkbox`].
+ pub fn text_wrapping(mut self, wrapping: text::Wrapping) -> Self {
+ self.text_wrapping = wrapping;
+ self
+ }
+
/// Sets the [`Renderer::Font`] of the text of the [`Checkbox`].
///
/// [`Renderer::Font`]: crate::core::text::Renderer
@@ -240,6 +248,7 @@ where
alignment::Horizontal::Left,
alignment::Vertical::Top,
self.text_shaping,
+ self.text_wrapping,
)
},
)
@@ -348,6 +357,7 @@ where
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
shaping: *shaping,
+ wrapping: text::Wrapping::default(),
},
bounds.center(),
style.icon_color,
diff --git a/widget/src/container.rs b/widget/src/container.rs
index 54043ad0..c3a66360 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -184,7 +184,6 @@ where
}
/// Sets the style class of the [`Container`].
- #[cfg(feature = "advanced")]
#[must_use]
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
self.class = class.into();
@@ -613,6 +612,12 @@ pub trait Catalog {
/// A styling function for a [`Container`].
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+impl<'a, Theme> From<Style> for StyleFn<'a, Theme> {
+ fn from(style: Style) -> Self {
+ Box::new(move |_theme| style)
+ }
+}
+
impl Catalog for Theme {
type Class<'a> = StyleFn<'a, Self>;
@@ -630,6 +635,11 @@ pub fn transparent<Theme>(_theme: &Theme) -> Style {
Style::default()
}
+/// A [`Container`] with the given [`Background`].
+pub fn background(background: impl Into<Background>) -> Style {
+ Style::default().background(background)
+}
+
/// A rounded [`Container`] with a background.
pub fn rounded_box(theme: &Theme) -> Style {
let palette = theme.extended_palette();
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index c3ffea45..349f02a6 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -25,7 +25,7 @@ use crate::tooltip::{self, Tooltip};
use crate::vertical_slider::{self, VerticalSlider};
use crate::{Column, MouseArea, Row, Space, Stack, Themer};
-use std::borrow::{Borrow, Cow};
+use std::borrow::Borrow;
use std::ops::RangeInclusive;
/// Creates a [`Column`] with the given children.
@@ -707,12 +707,13 @@ where
///
/// [`Rich`]: text::Rich
pub fn rich_text<'a, Link, Theme, Renderer>(
- spans: impl Into<Cow<'a, [text::Span<'a, Link, Renderer::Font>]>>,
+ spans: impl AsRef<[text::Span<'a, Link, Renderer::Font>]> + 'a,
) -> text::Rich<'a, Link, Theme, Renderer>
where
Link: Clone + 'static,
Theme: text::Catalog + 'a,
Renderer: core::text::Renderer,
+ Renderer::Font: 'a,
{
text::Rich::with_spans(spans)
}
@@ -766,7 +767,7 @@ where
///
/// [`Toggler`]: crate::Toggler
pub fn toggler<'a, Message, Theme, Renderer>(
- label: impl Into<Option<String>>,
+ label: Option<impl text::IntoFragment<'a>>,
is_checked: bool,
f: impl Fn(bool) -> Message + 'a,
) -> Toggler<'a, Message, Theme, Renderer>
diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs
index 4bcf8628..221f9de3 100644
--- a/widget/src/lazy.rs
+++ b/widget/src/lazy.rs
@@ -4,6 +4,7 @@ pub(crate) mod helpers;
pub mod component;
pub mod responsive;
+#[allow(deprecated)]
pub use component::Component;
pub use responsive::Responsive;
@@ -29,6 +30,7 @@ use std::hash::{Hash, Hasher as H};
use std::rc::Rc;
/// A widget that only rebuilds its contents when necessary.
+#[cfg(feature = "lazy")]
#[allow(missing_debug_implementations)]
pub struct Lazy<'a, Message, Theme, Renderer, Dependency, View> {
dependency: Dependency,
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index 1bf04195..659bc476 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -1,4 +1,5 @@
//! Build and reuse custom widgets using The Elm Architecture.
+#![allow(deprecated)]
use crate::core::event;
use crate::core::layout::{self, Layout};
use crate::core::mouse;
@@ -30,6 +31,12 @@ use std::rc::Rc;
///
/// Additionally, a [`Component`] is capable of producing a `Message` to notify
/// the parent application of any relevant interactions.
+#[cfg(feature = "lazy")]
+#[deprecated(
+ since = "0.13.0",
+ note = "components introduce encapsulated state and hamper the use of a single source of truth. \
+ Instead, leverage the Elm Architecture directly, or implement a custom widget"
+)]
pub trait Component<Message, Theme = crate::Theme, Renderer = crate::Renderer> {
/// The internal state of this [`Component`].
type State: Default;
diff --git a/widget/src/lazy/helpers.rs b/widget/src/lazy/helpers.rs
index 4d0776ca..52e690ff 100644
--- a/widget/src/lazy/helpers.rs
+++ b/widget/src/lazy/helpers.rs
@@ -1,9 +1,11 @@
use crate::core::{self, Element, Size};
-use crate::lazy::component::{self, Component};
-use crate::lazy::{Lazy, Responsive};
+use crate::lazy::component;
use std::hash::Hash;
+#[allow(deprecated)]
+pub use crate::lazy::{Component, Lazy, Responsive};
+
/// Creates a new [`Lazy`] widget with the given data `Dependency` and a
/// closure that can turn this data into a widget tree.
#[cfg(feature = "lazy")]
@@ -21,6 +23,12 @@ where
/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.
#[cfg(feature = "lazy")]
+#[deprecated(
+ since = "0.13.0",
+ note = "components introduce encapsulated state and hamper the use of a single source of truth. \
+ Instead, leverage the Elm Architecture directly, or implement a custom widget"
+)]
+#[allow(deprecated)]
pub fn component<'a, C, Message, Theme, Renderer>(
component: C,
) -> Element<'a, Message, Theme, Renderer>
diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs
index 2e24f2b3..dbf281f3 100644
--- a/widget/src/lazy/responsive.rs
+++ b/widget/src/lazy/responsive.rs
@@ -21,6 +21,7 @@ use std::ops::Deref;
///
/// A [`Responsive`] widget will always try to fill all the available space of
/// its parent.
+#[cfg(feature = "lazy")]
#[allow(missing_debug_implementations)]
pub struct Responsive<
'a,
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index 115a29e5..a68720d6 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -43,9 +43,6 @@ pub use helpers::*;
mod lazy;
#[cfg(feature = "lazy")]
-pub use crate::lazy::{Component, Lazy, Responsive};
-
-#[cfg(feature = "lazy")]
pub use crate::lazy::helpers::*;
#[doc(no_inline)]
diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs
index 23e36435..fa4ee6bf 100644
--- a/widget/src/markdown.rs
+++ b/widget/src/markdown.rs
@@ -7,10 +7,16 @@
use crate::core::border;
use crate::core::font::{self, Font};
use crate::core::padding;
-use crate::core::theme::{self, Theme};
-use crate::core::{self, color, Color, Element, Length, Pixels};
+use crate::core::theme;
+use crate::core::{
+ self, color, Color, Element, Length, Padding, Pixels, Theme,
+};
use crate::{column, container, rich_text, row, scrollable, span, text};
+use std::cell::{Cell, RefCell};
+use std::rc::Rc;
+
+pub use core::text::Highlight;
pub use pulldown_cmark::HeadingLevel;
pub use url::Url;
@@ -18,13 +24,13 @@ pub use url::Url;
#[derive(Debug, Clone)]
pub enum Item {
/// A heading.
- Heading(pulldown_cmark::HeadingLevel, Vec<text::Span<'static, Url>>),
+ Heading(pulldown_cmark::HeadingLevel, Text),
/// A paragraph.
- Paragraph(Vec<text::Span<'static, Url>>),
+ Paragraph(Text),
/// A code block.
///
/// You can enable the `highlighter` feature for syntax highligting.
- CodeBlock(Vec<text::Span<'static, Url>>),
+ CodeBlock(Text),
/// A list.
List {
/// The first number of the list, if it is ordered.
@@ -34,11 +40,112 @@ pub enum Item {
},
}
+/// A bunch of parsed Markdown text.
+#[derive(Debug, Clone)]
+pub struct Text {
+ spans: Vec<Span>,
+ last_style: Cell<Option<Style>>,
+ last_styled_spans: RefCell<Rc<[text::Span<'static, Url>]>>,
+}
+
+impl Text {
+ fn new(spans: Vec<Span>) -> Self {
+ Self {
+ spans,
+ last_style: Cell::default(),
+ last_styled_spans: RefCell::default(),
+ }
+ }
+
+ /// Returns the [`rich_text()`] spans ready to be used for the given style.
+ ///
+ /// This method performs caching for you. It will only reallocate if the [`Style`]
+ /// provided changes.
+ pub fn spans(&self, style: Style) -> Rc<[text::Span<'static, Url>]> {
+ if Some(style) != self.last_style.get() {
+ *self.last_styled_spans.borrow_mut() =
+ self.spans.iter().map(|span| span.view(&style)).collect();
+
+ self.last_style.set(Some(style));
+ }
+
+ self.last_styled_spans.borrow().clone()
+ }
+}
+
+#[derive(Debug, Clone)]
+enum Span {
+ Standard {
+ text: String,
+ strikethrough: bool,
+ link: Option<Url>,
+ strong: bool,
+ emphasis: bool,
+ code: bool,
+ },
+ #[cfg(feature = "highlighter")]
+ Highlight {
+ text: String,
+ color: Option<Color>,
+ font: Option<Font>,
+ },
+}
+
+impl Span {
+ fn view(&self, style: &Style) -> text::Span<'static, Url> {
+ match self {
+ Span::Standard {
+ text,
+ strikethrough,
+ link,
+ strong,
+ emphasis,
+ code,
+ } => {
+ let span = span(text.clone()).strikethrough(*strikethrough);
+
+ let span = if *code {
+ span.font(Font::MONOSPACE)
+ .color(style.inline_code_color)
+ .background(style.inline_code_highlight.background)
+ .border(style.inline_code_highlight.border)
+ .padding(style.inline_code_padding)
+ } else if *strong || *emphasis {
+ span.font(Font {
+ weight: if *strong {
+ font::Weight::Bold
+ } else {
+ font::Weight::Normal
+ },
+ style: if *emphasis {
+ font::Style::Italic
+ } else {
+ font::Style::Normal
+ },
+ ..Font::default()
+ })
+ } else {
+ span
+ };
+
+ let span = if let Some(link) = link.as_ref() {
+ span.color(style.link_color).link(link.clone())
+ } else {
+ span
+ };
+
+ span
+ }
+ #[cfg(feature = "highlighter")]
+ Span::Highlight { text, color, font } => {
+ span(text.clone()).color_maybe(*color).font_maybe(*font)
+ }
+ }
+ }
+}
+
/// Parse the given Markdown content.
-pub fn parse(
- markdown: &str,
- palette: theme::Palette,
-) -> impl Iterator<Item = Item> + '_ {
+pub fn parse(markdown: &str) -> impl Iterator<Item = Item> + '_ {
struct List {
start: Option<u64>,
items: Vec<Vec<Item>>,
@@ -158,7 +265,7 @@ pub fn parse(
pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => {
produce(
&mut lists,
- Item::Heading(level, spans.drain(..).collect()),
+ Item::Heading(level, Text::new(spans.drain(..).collect())),
)
}
pulldown_cmark::TagEnd::Strong if !metadata && !table => {
@@ -178,7 +285,10 @@ pub fn parse(
None
}
pulldown_cmark::TagEnd::Paragraph if !metadata && !table => {
- produce(&mut lists, Item::Paragraph(spans.drain(..).collect()))
+ produce(
+ &mut lists,
+ Item::Paragraph(Text::new(spans.drain(..).collect())),
+ )
}
pulldown_cmark::TagEnd::Item if !metadata && !table => {
if spans.is_empty() {
@@ -186,7 +296,7 @@ pub fn parse(
} else {
produce(
&mut lists,
- Item::Paragraph(spans.drain(..).collect()),
+ Item::Paragraph(Text::new(spans.drain(..).collect())),
)
}
}
@@ -207,7 +317,10 @@ pub fn parse(
highlighter = None;
}
- produce(&mut lists, Item::CodeBlock(spans.drain(..).collect()))
+ produce(
+ &mut lists,
+ Item::CodeBlock(Text::new(spans.drain(..).collect())),
+ )
}
pulldown_cmark::TagEnd::MetadataBlock(_) => {
metadata = false;
@@ -227,9 +340,11 @@ pub fn parse(
for (range, highlight) in
highlighter.highlight_line(text.as_ref())
{
- let span = span(text[range].to_owned())
- .color_maybe(highlight.color())
- .font_maybe(highlight.font());
+ let span = Span::Highlight {
+ text: text[range].to_owned(),
+ color: highlight.color(),
+ font: highlight.font(),
+ };
spans.push(span);
}
@@ -237,30 +352,13 @@ pub fn parse(
return None;
}
- let span = span(text.into_string()).strikethrough(strikethrough);
-
- let span = if strong || emphasis {
- span.font(Font {
- weight: if strong {
- font::Weight::Bold
- } else {
- font::Weight::Normal
- },
- style: if emphasis {
- font::Style::Italic
- } else {
- font::Style::Normal
- },
- ..Font::default()
- })
- } else {
- span
- };
-
- let span = if let Some(link) = link.as_ref() {
- span.color(palette.primary).link(link.clone())
- } else {
- span
+ let span = Span::Standard {
+ text: text.into_string(),
+ strong,
+ emphasis,
+ strikethrough,
+ link: link.clone(),
+ code: false,
};
spans.push(span);
@@ -268,29 +366,38 @@ pub fn parse(
None
}
pulldown_cmark::Event::Code(code) if !metadata && !table => {
- let span = span(code.into_string())
- .font(Font::MONOSPACE)
- .color(Color::WHITE)
- .background(color!(0x111111))
- .border(border::rounded(2))
- .padding(padding::left(2).right(2))
- .strikethrough(strikethrough);
-
- let span = if let Some(link) = link.as_ref() {
- span.color(palette.primary).link(link.clone())
- } else {
- span
+ let span = Span::Standard {
+ text: code.into_string(),
+ strong,
+ emphasis,
+ strikethrough,
+ link: link.clone(),
+ code: true,
};
spans.push(span);
None
}
pulldown_cmark::Event::SoftBreak if !metadata && !table => {
- spans.push(span(" ").strikethrough(strikethrough));
+ spans.push(Span::Standard {
+ text: String::from(" "),
+ strikethrough,
+ strong,
+ emphasis,
+ link: link.clone(),
+ code: false,
+ });
None
}
pulldown_cmark::Event::HardBreak if !metadata && !table => {
- spans.push(span("\n"));
+ spans.push(Span::Standard {
+ text: String::from("\n"),
+ strikethrough,
+ strong,
+ emphasis,
+ link: link.clone(),
+ code: false,
+ });
None
}
_ => None,
@@ -346,14 +453,44 @@ impl Default for Settings {
}
}
+/// The text styling of some Markdown rendering in [`view`].
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Style {
+ /// The [`Highlight`] to be applied to the background of inline code.
+ pub inline_code_highlight: Highlight,
+ /// The [`Padding`] to be applied to the background of inline code.
+ pub inline_code_padding: Padding,
+ /// The [`Color`] to be applied to inline code.
+ pub inline_code_color: Color,
+ /// The [`Color`] to be applied to links.
+ pub link_color: Color,
+}
+
+impl Style {
+ /// Creates a new [`Style`] from the given [`theme::Palette`].
+ pub fn from_palette(palette: theme::Palette) -> Self {
+ Self {
+ inline_code_padding: padding::left(1).right(1),
+ inline_code_highlight: Highlight {
+ background: color!(0x111).into(),
+ border: border::rounded(2),
+ },
+ inline_code_color: Color::WHITE,
+ link_color: palette.primary,
+ }
+ }
+}
+
/// Display a bunch of Markdown items.
///
/// You can obtain the items with [`parse`].
-pub fn view<'a, Renderer>(
+pub fn view<'a, Theme, Renderer>(
items: impl IntoIterator<Item = &'a Item>,
settings: Settings,
+ style: Style,
) -> Element<'a, Url, Theme, Renderer>
where
+ Theme: Catalog + 'a,
Renderer: core::text::Renderer<Font = Font> + 'a,
{
let Settings {
@@ -371,7 +508,7 @@ where
let blocks = items.into_iter().enumerate().map(|(i, item)| match item {
Item::Heading(level, heading) => {
- container(rich_text(heading).size(match level {
+ container(rich_text(heading.spans(style)).size(match level {
pulldown_cmark::HeadingLevel::H1 => h1_size,
pulldown_cmark::HeadingLevel::H2 => h2_size,
pulldown_cmark::HeadingLevel::H3 => h3_size,
@@ -387,11 +524,11 @@ where
.into()
}
Item::Paragraph(paragraph) => {
- rich_text(paragraph).size(text_size).into()
+ rich_text(paragraph.spans(style)).size(text_size).into()
}
Item::List { start: None, items } => {
column(items.iter().map(|items| {
- row![text("•").size(text_size), view(items, settings)]
+ row![text("•").size(text_size), view(items, settings, style)]
.spacing(spacing)
.into()
}))
@@ -404,7 +541,7 @@ where
} => column(items.iter().enumerate().map(|(i, items)| {
row![
text!("{}.", i as u64 + *start).size(text_size),
- view(items, settings)
+ view(items, settings, style)
]
.spacing(spacing)
.into()
@@ -414,7 +551,9 @@ where
Item::CodeBlock(code) => container(
scrollable(
container(
- rich_text(code).font(Font::MONOSPACE).size(code_size),
+ rich_text(code.spans(style))
+ .font(Font::MONOSPACE)
+ .size(code_size),
)
.padding(spacing.0 / 2.0),
)
@@ -426,9 +565,23 @@ where
)
.width(Length::Fill)
.padding(spacing.0 / 2.0)
- .style(container::dark)
+ .class(Theme::code_block())
.into(),
});
Element::new(column(blocks).width(Length::Fill).spacing(text_size))
}
+
+/// The theme catalog of Markdown items.
+pub trait Catalog:
+ container::Catalog + scrollable::Catalog + text::Catalog
+{
+ /// The styling class of a Markdown code block.
+ fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;
+}
+
+impl Catalog for Theme {
+ fn code_block<'a>() -> <Self as container::Catalog>::Class<'a> {
+ Box::new(container::dark)
+ }
+}
diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index 73d1cc8c..f05ae40a 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -532,6 +532,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: self.text_shaping,
+ wrapping: text::Wrapping::default(),
},
Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected {
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 0aab1ab5..710a5443 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -10,6 +10,7 @@
mod axis;
mod configuration;
mod content;
+mod controls;
mod direction;
mod draggable;
mod node;
@@ -22,6 +23,7 @@ pub mod state;
pub use axis::Axis;
pub use configuration::Configuration;
pub use content::Content;
+pub use controls::Controls;
pub use direction::Direction;
pub use draggable::Draggable;
pub use node::Node;
diff --git a/widget/src/pane_grid/controls.rs b/widget/src/pane_grid/controls.rs
new file mode 100644
index 00000000..13b57acb
--- /dev/null
+++ b/widget/src/pane_grid/controls.rs
@@ -0,0 +1,59 @@
+use crate::container;
+use crate::core::{self, Element};
+
+/// The controls of a [`Pane`].
+///
+/// [`Pane`]: super::Pane
+#[allow(missing_debug_implementations)]
+pub struct Controls<
+ 'a,
+ Message,
+ Theme = crate::Theme,
+ Renderer = crate::Renderer,
+> where
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
+{
+ pub(super) full: Element<'a, Message, Theme, Renderer>,
+ pub(super) compact: Option<Element<'a, Message, Theme, Renderer>>,
+}
+
+impl<'a, Message, Theme, Renderer> Controls<'a, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
+{
+ /// Creates a new [`Controls`] with the given content.
+ pub fn new(
+ content: impl Into<Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ Self {
+ full: content.into(),
+ compact: None,
+ }
+ }
+
+ /// Creates a new [`Controls`] with a full and compact variant.
+ /// If there is not enough room to show the full variant without overlap,
+ /// then the compact variant will be shown instead.
+ pub fn dynamic(
+ full: impl Into<Element<'a, Message, Theme, Renderer>>,
+ compact: impl Into<Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ Self {
+ full: full.into(),
+ compact: Some(compact.into()),
+ }
+ }
+}
+
+impl<'a, Message, Theme, Renderer> From<Element<'a, Message, Theme, Renderer>>
+ for Controls<'a, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
+{
+ fn from(value: Element<'a, Message, Theme, Renderer>) -> Self {
+ Self::new(value)
+ }
+}
diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index 791fab4a..5002b4f7 100644
--- a/widget/src/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -9,6 +9,7 @@ use crate::core::{
self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
Vector,
};
+use crate::pane_grid::controls::Controls;
/// The title bar of a [`Pane`].
///
@@ -24,7 +25,7 @@ pub struct TitleBar<
Renderer: core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
- controls: Option<Element<'a, Message, Theme, Renderer>>,
+ controls: Option<Controls<'a, Message, Theme, Renderer>>,
padding: Padding,
always_show_controls: bool,
class: Theme::Class<'a>,
@@ -51,7 +52,7 @@ where
/// Sets the controls of the [`TitleBar`].
pub fn controls(
mut self,
- controls: impl Into<Element<'a, Message, Theme, Renderer>>,
+ controls: impl Into<Controls<'a, Message, Theme, Renderer>>,
) -> Self {
self.controls = Some(controls.into());
self
@@ -104,10 +105,22 @@ where
Renderer: core::Renderer,
{
pub(super) fn state(&self) -> Tree {
- let children = if let Some(controls) = self.controls.as_ref() {
- vec![Tree::new(&self.content), Tree::new(controls)]
- } else {
- vec![Tree::new(&self.content), Tree::empty()]
+ let children = match self.controls.as_ref() {
+ Some(controls) => match controls.compact.as_ref() {
+ Some(compact) => vec![
+ Tree::new(&self.content),
+ Tree::new(&controls.full),
+ Tree::new(compact),
+ ],
+ None => vec![
+ Tree::new(&self.content),
+ Tree::new(&controls.full),
+ Tree::empty(),
+ ],
+ },
+ None => {
+ vec![Tree::new(&self.content), Tree::empty(), Tree::empty()]
+ }
};
Tree {
@@ -117,9 +130,13 @@ where
}
pub(super) fn diff(&self, tree: &mut Tree) {
- if tree.children.len() == 2 {
+ if tree.children.len() == 3 {
if let Some(controls) = self.controls.as_ref() {
- tree.children[1].diff(controls);
+ if let Some(compact) = controls.compact.as_ref() {
+ tree.children[2].diff(compact);
+ }
+
+ tree.children[1].diff(&controls.full);
}
tree.children[0].diff(&self.content);
@@ -164,18 +181,42 @@ where
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
- show_title = false;
+ if let Some(compact) = controls.compact.as_ref() {
+ let compact_layout = children.next().unwrap();
+
+ compact.as_widget().draw(
+ &tree.children[2],
+ renderer,
+ theme,
+ &inherited_style,
+ compact_layout,
+ cursor,
+ viewport,
+ );
+ } else {
+ show_title = false;
+
+ controls.full.as_widget().draw(
+ &tree.children[1],
+ renderer,
+ theme,
+ &inherited_style,
+ controls_layout,
+ cursor,
+ viewport,
+ );
+ }
+ } else {
+ controls.full.as_widget().draw(
+ &tree.children[1],
+ renderer,
+ theme,
+ &inherited_style,
+ controls_layout,
+ cursor,
+ viewport,
+ );
}
-
- controls.as_widget().draw(
- &tree.children[1],
- renderer,
- theme,
- &inherited_style,
- controls_layout,
- cursor,
- viewport,
- );
}
}
@@ -207,13 +248,20 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
- if self.controls.is_some() {
+ if let Some(controls) = self.controls.as_ref() {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
- !controls_layout.bounds().contains(cursor_position)
+ if controls.compact.is_some() {
+ let compact_layout = children.next().unwrap();
+
+ !compact_layout.bounds().contains(cursor_position)
+ && !title_layout.bounds().contains(cursor_position)
+ } else {
+ !controls_layout.bounds().contains(cursor_position)
+ }
} else {
!controls_layout.bounds().contains(cursor_position)
&& !title_layout.bounds().contains(cursor_position)
@@ -244,25 +292,73 @@ where
let title_size = title_layout.size();
let node = if let Some(controls) = &self.controls {
- let controls_layout = controls.as_widget().layout(
+ let controls_layout = controls.full.as_widget().layout(
&mut tree.children[1],
renderer,
&layout::Limits::new(Size::ZERO, max_size),
);
- let controls_size = controls_layout.size();
- let space_before_controls = max_size.width - controls_size.width;
-
- let height = title_size.height.max(controls_size.height);
-
- layout::Node::with_children(
- Size::new(max_size.width, height),
- vec![
- title_layout,
- controls_layout
- .move_to(Point::new(space_before_controls, 0.0)),
- ],
- )
+ if title_layout.bounds().width + controls_layout.bounds().width
+ > max_size.width
+ {
+ if let Some(compact) = controls.compact.as_ref() {
+ let compact_layout = compact.as_widget().layout(
+ &mut tree.children[2],
+ renderer,
+ &layout::Limits::new(Size::ZERO, max_size),
+ );
+
+ let compact_size = compact_layout.size();
+ let space_before_controls =
+ max_size.width - compact_size.width;
+
+ let height = title_size.height.max(compact_size.height);
+
+ layout::Node::with_children(
+ Size::new(max_size.width, height),
+ vec![
+ title_layout,
+ controls_layout,
+ compact_layout.move_to(Point::new(
+ space_before_controls,
+ 0.0,
+ )),
+ ],
+ )
+ } else {
+ let controls_size = controls_layout.size();
+ let space_before_controls =
+ max_size.width - controls_size.width;
+
+ let height = title_size.height.max(controls_size.height);
+
+ layout::Node::with_children(
+ Size::new(max_size.width, height),
+ vec![
+ title_layout,
+ controls_layout.move_to(Point::new(
+ space_before_controls,
+ 0.0,
+ )),
+ ],
+ )
+ }
+ } else {
+ let controls_size = controls_layout.size();
+ let space_before_controls =
+ max_size.width - controls_size.width;
+
+ let height = title_size.height.max(controls_size.height);
+
+ layout::Node::with_children(
+ Size::new(max_size.width, height),
+ vec![
+ title_layout,
+ controls_layout
+ .move_to(Point::new(space_before_controls, 0.0)),
+ ],
+ )
+ }
} else {
layout::Node::with_children(
Size::new(max_size.width, title_size.height),
@@ -293,15 +389,33 @@ where
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
- show_title = false;
- }
+ if let Some(compact) = controls.compact.as_ref() {
+ let compact_layout = children.next().unwrap();
- controls.as_widget().operate(
- &mut tree.children[1],
- controls_layout,
- renderer,
- operation,
- );
+ compact.as_widget().operate(
+ &mut tree.children[2],
+ compact_layout,
+ renderer,
+ operation,
+ );
+ } else {
+ show_title = false;
+
+ controls.full.as_widget().operate(
+ &mut tree.children[1],
+ controls_layout,
+ renderer,
+ operation,
+ );
+ }
+ } else {
+ controls.full.as_widget().operate(
+ &mut tree.children[1],
+ controls_layout,
+ renderer,
+ operation,
+ );
+ }
};
if show_title {
@@ -337,19 +451,45 @@ where
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
- show_title = false;
- }
+ if let Some(compact) = controls.compact.as_mut() {
+ let compact_layout = children.next().unwrap();
+
+ compact.as_widget_mut().on_event(
+ &mut tree.children[2],
+ event.clone(),
+ compact_layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ )
+ } else {
+ show_title = false;
- controls.as_widget_mut().on_event(
- &mut tree.children[1],
- event.clone(),
- controls_layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
- )
+ controls.full.as_widget_mut().on_event(
+ &mut tree.children[1],
+ event.clone(),
+ controls_layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ )
+ }
+ } else {
+ controls.full.as_widget_mut().on_event(
+ &mut tree.children[1],
+ event.clone(),
+ controls_layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ )
+ }
} else {
event::Status::Ignored
};
@@ -396,18 +536,33 @@ where
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
- let controls_interaction = controls.as_widget().mouse_interaction(
- &tree.children[1],
- controls_layout,
- cursor,
- viewport,
- renderer,
- );
+ let controls_interaction =
+ controls.full.as_widget().mouse_interaction(
+ &tree.children[1],
+ controls_layout,
+ cursor,
+ viewport,
+ renderer,
+ );
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
- controls_interaction
+ if let Some(compact) = controls.compact.as_ref() {
+ let compact_layout = children.next().unwrap();
+ let compact_interaction =
+ compact.as_widget().mouse_interaction(
+ &tree.children[2],
+ compact_layout,
+ cursor,
+ viewport,
+ renderer,
+ );
+
+ compact_interaction.max(title_interaction)
+ } else {
+ controls_interaction
+ }
} else {
controls_interaction.max(title_interaction)
}
@@ -444,12 +599,36 @@ where
controls.as_mut().and_then(|controls| {
let controls_layout = children.next()?;
- controls.as_widget_mut().overlay(
- controls_state,
- controls_layout,
- renderer,
- translation,
- )
+ if title_layout.bounds().width
+ + controls_layout.bounds().width
+ > padded.bounds().width
+ {
+ if let Some(compact) = controls.compact.as_mut() {
+ let compact_state = states.next().unwrap();
+ let compact_layout = children.next()?;
+
+ compact.as_widget_mut().overlay(
+ compact_state,
+ compact_layout,
+ renderer,
+ translation,
+ )
+ } else {
+ controls.full.as_widget_mut().overlay(
+ controls_state,
+ controls_layout,
+ renderer,
+ translation,
+ )
+ }
+ } else {
+ controls.full.as_widget_mut().overlay(
+ controls_state,
+ controls_layout,
+ renderer,
+ translation,
+ )
+ }
})
})
}
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index f7f7b65b..1fc9951e 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -81,7 +81,7 @@ where
padding: crate::button::DEFAULT_PADDING,
text_size: None,
text_line_height: text::LineHeight::default(),
- text_shaping: text::Shaping::Basic,
+ text_shaping: text::Shaping::default(),
font: None,
handle: Handle::default(),
class: <Theme as Catalog>::default(),
@@ -250,6 +250,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: self.text_shaping,
+ wrapping: text::Wrapping::default(),
};
for (option, paragraph) in options.iter().zip(state.options.iter_mut())
@@ -515,6 +516,7 @@ where
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Center,
shaping,
+ wrapping: text::Wrapping::default(),
},
Point::new(
bounds.x + bounds.width - self.padding.right,
@@ -544,6 +546,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: self.text_shaping,
+ wrapping: text::Wrapping::default(),
},
Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected {
diff --git a/widget/src/radio.rs b/widget/src/radio.rs
index 1b02f8ca..cfa961f3 100644
--- a/widget/src/radio.rs
+++ b/widget/src/radio.rs
@@ -82,6 +82,7 @@ where
text_size: Option<Pixels>,
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
+ text_wrapping: text::Wrapping,
font: Option<Renderer::Font>,
class: Theme::Class<'a>,
}
@@ -122,10 +123,11 @@ where
label: label.into(),
width: Length::Shrink,
size: Self::DEFAULT_SIZE,
- spacing: Self::DEFAULT_SPACING, //15
+ spacing: Self::DEFAULT_SPACING,
text_size: None,
text_line_height: text::LineHeight::default(),
- text_shaping: text::Shaping::Basic,
+ text_shaping: text::Shaping::default(),
+ text_wrapping: text::Wrapping::default(),
font: None,
class: Theme::default(),
}
@@ -170,6 +172,12 @@ where
self
}
+ /// Sets the [`text::Wrapping`] strategy of the [`Radio`] button.
+ pub fn text_wrapping(mut self, wrapping: text::Wrapping) -> Self {
+ self.text_wrapping = wrapping;
+ self
+ }
+
/// Sets the text font of the [`Radio`] button.
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
@@ -245,6 +253,7 @@ where
alignment::Horizontal::Left,
alignment::Vertical::Top,
self.text_shaping,
+ self.text_wrapping,
)
},
)
diff --git a/widget/src/stack.rs b/widget/src/stack.rs
index 001376ac..9ccaa274 100644
--- a/widget/src/stack.rs
+++ b/widget/src/stack.rs
@@ -209,19 +209,23 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ mut cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
+ let is_over_scroll =
+ matches!(event, Event::Mouse(mouse::Event::WheelScrolled { .. }))
+ && cursor.is_over(layout.bounds());
+
self.children
.iter_mut()
.rev()
.zip(tree.children.iter_mut().rev())
.zip(layout.children().rev())
.map(|((child, state), layout)| {
- child.as_widget_mut().on_event(
+ let status = child.as_widget_mut().on_event(
state,
event.clone(),
layout,
@@ -230,7 +234,19 @@ where
clipboard,
shell,
viewport,
- )
+ );
+
+ if is_over_scroll && cursor != mouse::Cursor::Unavailable {
+ let interaction = child.as_widget().mouse_interaction(
+ state, layout, cursor, viewport, renderer,
+ );
+
+ if interaction != mouse::Interaction::None {
+ cursor = mouse::Cursor::Unavailable;
+ }
+ }
+
+ status
})
.find(|&status| status == event::Status::Captured)
.unwrap_or(event::Status::Ignored)
@@ -269,15 +285,53 @@ where
viewport: &Rectangle,
) {
if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
- for (i, ((layer, state), layout)) in self
+ let layers_below = if cursor.is_over(layout.bounds()) {
+ self.children
+ .iter()
+ .rev()
+ .zip(tree.children.iter().rev())
+ .zip(layout.children().rev())
+ .position(|((layer, state), layout)| {
+ let interaction = layer.as_widget().mouse_interaction(
+ state, layout, cursor, viewport, renderer,
+ );
+
+ interaction != mouse::Interaction::None
+ })
+ .map(|i| self.children.len() - i - 1)
+ .unwrap_or_default()
+ } else {
+ 0
+ };
+
+ let mut layers = self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
- .enumerate()
- {
- if i > 0 {
- renderer.with_layer(clipped_viewport, |renderer| {
+ .enumerate();
+
+ let layers = layers.by_ref();
+
+ let mut draw_layer =
+ |i,
+ layer: &Element<'a, Message, Theme, Renderer>,
+ state,
+ layout,
+ cursor| {
+ if i > 0 {
+ renderer.with_layer(clipped_viewport, |renderer| {
+ layer.as_widget().draw(
+ state,
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor,
+ &clipped_viewport,
+ );
+ });
+ } else {
layer.as_widget().draw(
state,
renderer,
@@ -287,18 +341,15 @@ where
cursor,
&clipped_viewport,
);
- });
- } else {
- layer.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor,
- &clipped_viewport,
- );
- }
+ }
+ };
+
+ for (i, ((layer, state), layout)) in layers.take(layers_below) {
+ draw_layer(i, layer, state, layout, mouse::Cursor::Unavailable);
+ }
+
+ for (i, ((layer, state), layout)) in layers {
+ draw_layer(i, layer, state, layout, cursor);
}
}
}
diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs
index c6aa1e14..921c55a5 100644
--- a/widget/src/text/rich.rs
+++ b/widget/src/text/rich.rs
@@ -5,7 +5,7 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::text::{Paragraph, Span};
use crate::core::widget::text::{
- self, Catalog, LineHeight, Shaping, Style, StyleFn,
+ self, Catalog, LineHeight, Shaping, Style, StyleFn, Wrapping,
};
use crate::core::widget::tree::{self, Tree};
use crate::core::{
@@ -13,8 +13,6 @@ use crate::core::{
Rectangle, Shell, Size, Vector, Widget,
};
-use std::borrow::Cow;
-
/// A bunch of [`Rich`] text.
#[allow(missing_debug_implementations)]
pub struct Rich<'a, Link, Theme = crate::Theme, Renderer = crate::Renderer>
@@ -23,7 +21,7 @@ where
Theme: Catalog,
Renderer: core::text::Renderer,
{
- spans: Cow<'a, [Span<'a, Link, Renderer::Font>]>,
+ spans: Box<dyn AsRef<[Span<'a, Link, Renderer::Font>]> + 'a>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@@ -31,6 +29,7 @@ where
font: Option<Renderer::Font>,
align_x: alignment::Horizontal,
align_y: alignment::Vertical,
+ wrapping: Wrapping,
class: Theme::Class<'a>,
}
@@ -39,11 +38,12 @@ where
Link: Clone + 'static,
Theme: Catalog,
Renderer: core::text::Renderer,
+ Renderer::Font: 'a,
{
/// Creates a new empty [`Rich`] text.
pub fn new() -> Self {
Self {
- spans: Cow::default(),
+ spans: Box::new([]),
size: None,
line_height: LineHeight::default(),
width: Length::Shrink,
@@ -51,16 +51,17 @@ where
font: None,
align_x: alignment::Horizontal::Left,
align_y: alignment::Vertical::Top,
+ wrapping: Wrapping::default(),
class: Theme::default(),
}
}
/// Creates a new [`Rich`] text with the given text spans.
pub fn with_spans(
- spans: impl Into<Cow<'a, [Span<'a, Link, Renderer::Font>]>>,
+ spans: impl AsRef<[Span<'a, Link, Renderer::Font>]> + 'a,
) -> Self {
Self {
- spans: spans.into(),
+ spans: Box::new(spans),
..Self::new()
}
}
@@ -119,6 +120,12 @@ where
self
}
+ /// Sets the [`Wrapping`] strategy of the [`Rich`] text.
+ pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
+ self.wrapping = wrapping;
+ self
+ }
+
/// Sets the default style of the [`Rich`] text.
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
@@ -154,15 +161,6 @@ where
self.class = class.into();
self
}
-
- /// Adds a new text [`Span`] to the [`Rich`] text.
- pub fn push(
- mut self,
- span: impl Into<Span<'a, Link, Renderer::Font>>,
- ) -> Self {
- self.spans.to_mut().push(span.into());
- self
- }
}
impl<'a, Link, Theme, Renderer> Default for Rich<'a, Link, Theme, Renderer>
@@ -170,6 +168,7 @@ where
Link: Clone + 'a,
Theme: Catalog,
Renderer: core::text::Renderer,
+ Renderer::Font: 'a,
{
fn default() -> Self {
Self::new()
@@ -221,12 +220,13 @@ where
limits,
self.width,
self.height,
- self.spans.as_ref(),
+ self.spans.as_ref().as_ref(),
self.line_height,
self.size,
self.font,
self.align_x,
self.align_y,
+ self.wrapping,
)
}
@@ -250,7 +250,7 @@ where
.position_in(layout.bounds())
.and_then(|position| state.paragraph.hit_span(position));
- for (index, span) in self.spans.iter().enumerate() {
+ for (index, span) in self.spans.as_ref().as_ref().iter().enumerate() {
let is_hovered_link =
span.link.is_some() && Some(index) == hovered_span;
@@ -394,6 +394,8 @@ where
Some(span) if span == span_pressed => {
if let Some(link) = self
.spans
+ .as_ref()
+ .as_ref()
.get(span)
.and_then(|span| span.link.clone())
{
@@ -427,7 +429,7 @@ where
if let Some(span) = state
.paragraph
.hit_span(position)
- .and_then(|span| self.spans.get(span))
+ .and_then(|span| self.spans.as_ref().as_ref().get(span))
{
if span.link.is_some() {
return mouse::Interaction::Pointer;
@@ -451,6 +453,7 @@ fn layout<Link, Renderer>(
font: Option<Renderer::Font>,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
+ wrapping: Wrapping,
) -> layout::Node
where
Link: Clone,
@@ -471,6 +474,7 @@ where
horizontal_alignment,
vertical_alignment,
shaping: Shaping::Advanced,
+ wrapping,
};
if state.spans != spans {
@@ -487,6 +491,7 @@ where
horizontal_alignment,
vertical_alignment,
shaping: Shaping::Advanced,
+ wrapping,
}) {
core::text::Difference::None => {}
core::text::Difference::Bounds => {
@@ -509,14 +514,12 @@ where
Link: Clone + 'a,
Theme: Catalog,
Renderer: core::text::Renderer,
+ Renderer::Font: 'a,
{
fn from_iter<T: IntoIterator<Item = Span<'a, Link, Renderer::Font>>>(
spans: T,
) -> Self {
- Self {
- spans: spans.into_iter().collect(),
- ..Self::new()
- }
+ Self::with_spans(spans.into_iter().collect::<Vec<_>>())
}
}
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index 745e3ae8..d1aa4640 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -9,7 +9,7 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::text::editor::{Cursor, Editor as _};
use crate::core::text::highlighter::{self, Highlighter};
-use crate::core::text::{self, LineHeight, Text};
+use crate::core::text::{self, LineHeight, Text, Wrapping};
use crate::core::time::{Duration, Instant};
use crate::core::widget::operation;
use crate::core::widget::{self, Widget};
@@ -47,6 +47,7 @@ pub struct TextEditor<
width: Length,
height: Length,
padding: Padding,
+ wrapping: Wrapping,
class: Theme::Class<'a>,
key_binding: Option<Box<dyn Fn(KeyPress) -> Option<Binding<Message>> + 'a>>,
on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
@@ -74,6 +75,7 @@ where
width: Length::Fill,
height: Length::Shrink,
padding: Padding::new(5.0),
+ wrapping: Wrapping::default(),
class: Theme::default(),
key_binding: None,
on_edit: None,
@@ -148,6 +150,12 @@ where
self
}
+ /// Sets the [`Wrapping`] strategy of the [`TextEditor`].
+ pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
+ self.wrapping = wrapping;
+ self
+ }
+
/// Highlights the [`TextEditor`] using the given syntax and theme.
#[cfg(feature = "highlighter")]
pub fn highlight(
@@ -186,6 +194,7 @@ where
width: self.width,
height: self.height,
padding: self.padding,
+ wrapping: self.wrapping,
class: self.class,
key_binding: self.key_binding,
on_edit: self.on_edit,
@@ -496,6 +505,7 @@ where
self.font.unwrap_or_else(|| renderer.default_font()),
self.text_size.unwrap_or_else(|| renderer.default_size()),
self.line_height,
+ self.wrapping,
state.highlighter.borrow_mut().deref_mut(),
);
@@ -784,6 +794,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
+ wrapping: self.wrapping,
},
text_bounds.position(),
style.placeholder,
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 173de136..92047381 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -19,7 +19,7 @@ use crate::core::keyboard::key;
use crate::core::layout;
use crate::core::mouse::{self, click};
use crate::core::renderer;
-use crate::core::text::paragraph;
+use crate::core::text::paragraph::{self, Paragraph as _};
use crate::core::text::{self, Text};
use crate::core::time::{Duration, Instant};
use crate::core::touch;
@@ -74,6 +74,7 @@ pub struct TextInput<
padding: Padding,
size: Option<Pixels>,
line_height: text::LineHeight,
+ alignment: alignment::Horizontal,
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>,
@@ -103,6 +104,7 @@ where
padding: DEFAULT_PADDING,
size: None,
line_height: text::LineHeight::default(),
+ alignment: alignment::Horizontal::Left,
on_input: None,
on_paste: None,
on_submit: None,
@@ -193,6 +195,15 @@ where
self
}
+ /// Sets the horizontal alignment of the [`TextInput`].
+ pub fn align_x(
+ mut self,
+ alignment: impl Into<alignment::Horizontal>,
+ ) -> Self {
+ self.alignment = alignment.into();
+ self
+ }
+
/// Sets the style of the [`TextInput`].
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
@@ -240,6 +251,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
+ wrapping: text::Wrapping::default(),
};
state.placeholder.update(placeholder_text);
@@ -264,6 +276,7 @@ where
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
+ wrapping: text::Wrapping::default(),
};
state.icon.update(icon_text);
@@ -384,11 +397,11 @@ where
position,
);
- let is_cursor_visible = ((focus.now - focus.updated_at)
- .as_millis()
- / CURSOR_BLINK_INTERVAL_MILLIS)
- % 2
- == 0;
+ let is_cursor_visible = !is_disabled
+ && ((focus.now - focus.updated_at).as_millis()
+ / CURSOR_BLINK_INTERVAL_MILLIS)
+ % 2
+ == 0;
let cursor = if is_cursor_visible {
Some((
@@ -457,9 +470,21 @@ where
};
let draw = |renderer: &mut Renderer, viewport| {
+ let paragraph = if text.is_empty() {
+ state.placeholder.raw()
+ } else {
+ state.value.raw()
+ };
+
+ let alignment_offset = alignment_offset(
+ text_bounds.width,
+ paragraph.min_width(),
+ self.alignment,
+ );
+
if let Some((cursor, color)) = cursor {
renderer.with_translation(
- Vector::new(-offset, 0.0),
+ Vector::new(alignment_offset - offset, 0.0),
|renderer| {
renderer.fill_quad(cursor, color);
},
@@ -469,13 +494,9 @@ where
}
renderer.fill_paragraph(
- if text.is_empty() {
- state.placeholder.raw()
- } else {
- state.value.raw()
- },
+ paragraph,
Point::new(text_bounds.x, text_bounds.center_y())
- - Vector::new(offset, 0.0),
+ + Vector::new(alignment_offset - offset, 0.0),
if text.is_empty() {
style.placeholder
} else {
@@ -512,12 +533,9 @@ where
fn diff(&self, tree: &mut Tree) {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
- // Unfocus text input if it becomes disabled
+ // Stop pasting if input becomes disabled
if self.on_input.is_none() {
- state.last_click = None;
- state.is_focused = None;
state.is_pasting = None;
- state.is_dragging = false;
}
}
@@ -578,11 +596,7 @@ where
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state::<Renderer>(tree);
- let click_position = if self.on_input.is_some() {
- cursor.position_over(layout.bounds())
- } else {
- None
- };
+ let click_position = cursor.position_over(layout.bounds());
state.is_focused = if click_position.is_some() {
state.is_focused.or_else(|| {
@@ -600,7 +614,18 @@ where
if let Some(cursor_position) = click_position {
let text_layout = layout.children().next().unwrap();
- let target = cursor_position.x - text_layout.bounds().x;
+
+ let target = {
+ let text_bounds = text_layout.bounds();
+
+ let alignment_offset = alignment_offset(
+ text_bounds.width,
+ state.value.raw().min_width(),
+ self.alignment,
+ );
+
+ cursor_position.x - text_bounds.x - alignment_offset
+ };
let click =
mouse::Click::new(cursor_position, state.last_click);
@@ -677,7 +702,18 @@ where
if state.is_dragging {
let text_layout = layout.children().next().unwrap();
- let target = position.x - text_layout.bounds().x;
+
+ let target = {
+ let text_bounds = text_layout.bounds();
+
+ let alignment_offset = alignment_offset(
+ text_bounds.width,
+ state.value.raw().min_width(),
+ self.alignment,
+ );
+
+ position.x - text_bounds.x - alignment_offset
+ };
let value = if self.is_secure {
self.value.secure()
@@ -706,10 +742,6 @@ where
let state = state::<Renderer>(tree);
if let Some(focus) = &mut state.is_focused {
- let Some(on_input) = &self.on_input else {
- return event::Status::Ignored;
- };
-
let modifiers = state.keyboard_modifiers;
focus.updated_at = Instant::now();
@@ -733,6 +765,10 @@ where
if state.keyboard_modifiers.command()
&& !self.is_secure =>
{
+ let Some(on_input) = &self.on_input else {
+ return event::Status::Ignored;
+ };
+
if let Some((start, end)) =
state.cursor.selection(&self.value)
{
@@ -757,6 +793,10 @@ where
if state.keyboard_modifiers.command()
&& !state.keyboard_modifiers.alt() =>
{
+ let Some(on_input) = &self.on_input else {
+ return event::Status::Ignored;
+ };
+
let content = match state.is_pasting.take() {
Some(content) => content,
None => {
@@ -800,6 +840,10 @@ where
}
if let Some(text) = text {
+ let Some(on_input) = &self.on_input else {
+ return event::Status::Ignored;
+ };
+
state.is_pasting = None;
if let Some(c) =
@@ -828,6 +872,10 @@ where
}
}
keyboard::Key::Named(key::Named::Backspace) => {
+ let Some(on_input) = &self.on_input else {
+ return event::Status::Ignored;
+ };
+
if modifiers.jump()
&& state.cursor.selection(&self.value).is_none()
{
@@ -852,6 +900,10 @@ where
update_cache(state, &self.value);
}
keyboard::Key::Named(key::Named::Delete) => {
+ let Some(on_input) = &self.on_input else {
+ return event::Status::Ignored;
+ };
+
if modifiers.jump()
&& state.cursor.selection(&self.value).is_none()
{
@@ -1070,7 +1122,7 @@ where
) -> mouse::Interaction {
if cursor.is_over(layout.bounds()) {
if self.on_input.is_none() {
- mouse::Interaction::NotAllowed
+ mouse::Interaction::Idle
} else {
mouse::Interaction::Text
}
@@ -1382,6 +1434,7 @@ fn replace_paragraph<Renderer>(
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Advanced,
+ wrapping: text::Wrapping::default(),
});
}
@@ -1486,3 +1539,21 @@ pub fn default(theme: &Theme, status: Status) -> Style {
},
}
}
+
+fn alignment_offset(
+ text_bounds_width: f32,
+ text_min_width: f32,
+ alignment: alignment::Horizontal,
+) -> f32 {
+ if text_min_width > text_bounds_width {
+ 0.0
+ } else {
+ match alignment {
+ alignment::Horizontal::Left => 0.0,
+ alignment::Horizontal::Center => {
+ (text_bounds_width - text_min_width) / 2.0
+ }
+ alignment::Horizontal::Right => text_bounds_width - text_min_width,
+ }
+ }
+}
diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs
index 821e2526..57e142e8 100644
--- a/widget/src/toggler.rs
+++ b/widget/src/toggler.rs
@@ -26,7 +26,7 @@ use crate::core::{
///
/// let is_toggled = true;
///
-/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b));
+/// Toggler::new(Some("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b));
/// ```
#[allow(missing_debug_implementations)]
pub struct Toggler<
@@ -40,13 +40,14 @@ pub struct Toggler<
{
is_toggled: bool,
on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
- label: Option<String>,
+ label: Option<text::Fragment<'a>>,
width: Length,
size: f32,
text_size: Option<Pixels>,
text_line_height: text::LineHeight,
text_alignment: alignment::Horizontal,
text_shaping: text::Shaping,
+ text_wrapping: text::Wrapping,
spacing: f32,
font: Option<Renderer::Font>,
class: Theme::Class<'a>,
@@ -69,7 +70,7 @@ where
/// will receive the new state of the [`Toggler`] and must produce a
/// `Message`.
pub fn new<F>(
- label: impl Into<Option<String>>,
+ label: Option<impl text::IntoFragment<'a>>,
is_toggled: bool,
f: F,
) -> Self
@@ -79,13 +80,14 @@ where
Toggler {
is_toggled,
on_toggle: Box::new(f),
- label: label.into(),
+ label: label.map(text::IntoFragment::into_fragment),
width: Length::Shrink,
size: Self::DEFAULT_SIZE,
text_size: None,
text_line_height: text::LineHeight::default(),
text_alignment: alignment::Horizontal::Left,
- text_shaping: text::Shaping::Basic,
+ text_shaping: text::Shaping::default(),
+ text_wrapping: text::Wrapping::default(),
spacing: Self::DEFAULT_SIZE / 2.0,
font: None,
class: Theme::default(),
@@ -131,6 +133,12 @@ where
self
}
+ /// Sets the [`text::Wrapping`] strategy of the [`Toggler`].
+ pub fn text_wrapping(mut self, wrapping: text::Wrapping) -> Self {
+ self.text_wrapping = wrapping;
+ self
+ }
+
/// Sets the spacing between the [`Toggler`] and the text.
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.spacing = spacing.into().0;
@@ -216,6 +224,7 @@ where
self.text_alignment,
alignment::Vertical::Top,
self.text_shaping,
+ self.text_wrapping,
)
} else {
layout::Node::new(Size::ZERO)
diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs
index 7ae646fc..d54a1fe0 100644
--- a/winit/src/clipboard.rs
+++ b/winit/src/clipboard.rs
@@ -2,7 +2,7 @@
use crate::core::clipboard::Kind;
use std::sync::Arc;
-use winit::window::Window;
+use winit::window::{Window, WindowId};
/// A buffer for short-term storage and transfer within and between
/// applications.
@@ -83,6 +83,14 @@ impl Clipboard {
State::Unavailable => {}
}
}
+
+ /// Returns the identifier of the window used to create the [`Clipboard`], if any.
+ pub fn window_id(&self) -> Option<WindowId> {
+ match &self.state {
+ State::Connected { window, .. } => Some(window.id()),
+ State::Unavailable => None,
+ }
+ }
}
impl crate::core::Clipboard for Clipboard {
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index e88ff84d..585e2409 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -79,6 +79,10 @@ pub fn window_attributes(
attributes = attributes
.with_skip_taskbar(settings.platform_specific.skip_taskbar);
+
+ attributes = attributes.with_undecorated_shadow(
+ settings.platform_specific.undecorated_shadow,
+ );
}
#[cfg(target_os = "macos")]
diff --git a/winit/src/program.rs b/winit/src/program.rs
index efe8a978..52d8eb5f 100644
--- a/winit/src/program.rs
+++ b/winit/src/program.rs
@@ -307,8 +307,6 @@ where
}
};
- let clipboard = Clipboard::connect(window.clone());
-
let finish_boot = async move {
let mut compositor =
C::new(graphics_settings, window.clone()).await?;
@@ -318,10 +316,7 @@ where
}
sender
- .send(Boot {
- compositor,
- clipboard,
- })
+ .send(Boot { compositor })
.ok()
.expect("Send boot event");
@@ -617,7 +612,6 @@ where
struct Boot<C> {
compositor: C,
- clipboard: Clipboard,
}
#[derive(Debug)]
@@ -650,7 +644,7 @@ async fn run_instance<P, C>(
mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
mut proxy: Proxy<P::Message>,
mut debug: Debug,
- mut boot: oneshot::Receiver<Boot<C>>,
+ boot: oneshot::Receiver<Boot<C>>,
mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>,
mut control_sender: mpsc::UnboundedSender<Control>,
is_daemon: bool,
@@ -662,10 +656,7 @@ async fn run_instance<P, C>(
use winit::event;
use winit::event_loop::ControlFlow;
- let Boot {
- mut compositor,
- mut clipboard,
- } = boot.try_recv().ok().flatten().expect("Receive boot");
+ let Boot { mut compositor } = boot.await.expect("Receive boot");
let mut window_manager = WindowManager::new();
let mut is_window_opening = !is_daemon;
@@ -676,10 +667,22 @@ async fn run_instance<P, C>(
let mut ui_caches = FxHashMap::default();
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
+ let mut clipboard = Clipboard::unconnected();
debug.startup_finished();
- while let Some(event) = event_receiver.next().await {
+ loop {
+ // Empty the queue if possible
+ let event = if let Ok(event) = event_receiver.try_next() {
+ event
+ } else {
+ event_receiver.next().await
+ };
+
+ let Some(event) = event else {
+ break;
+ };
+
match event {
Event::WindowCreated {
id,
@@ -723,6 +726,10 @@ async fn run_instance<P, C>(
}),
));
+ if clipboard.window_id().is_none() {
+ clipboard = Clipboard::connect(window.raw.clone());
+ }
+
let _ = on_open.send(id);
is_window_opening = false;
}
@@ -968,14 +975,22 @@ async fn run_instance<P, C>(
winit::event::WindowEvent::CloseRequested
) && window.exit_on_close_request
{
- let _ = window_manager.remove(id);
- let _ = user_interfaces.remove(&id);
- let _ = ui_caches.remove(&id);
-
- events.push((
- id,
- core::Event::Window(window::Event::Closed),
- ));
+ run_action(
+ Action::Window(runtime::window::Action::Close(
+ id,
+ )),
+ &program,
+ &mut compositor,
+ &mut events,
+ &mut messages,
+ &mut clipboard,
+ &mut control_sender,
+ &mut debug,
+ &mut user_interfaces,
+ &mut window_manager,
+ &mut ui_caches,
+ &mut is_window_opening,
+ );
} else {
window.state.update(
&window.raw,
@@ -1212,13 +1227,23 @@ fn run_action<P, C>(
*is_window_opening = true;
}
window::Action::Close(id) => {
- let _ = window_manager.remove(id);
let _ = ui_caches.remove(&id);
+ let _ = interfaces.remove(&id);
+
+ if let Some(window) = window_manager.remove(id) {
+ if clipboard.window_id() == Some(window.raw.id()) {
+ *clipboard = window_manager
+ .first()
+ .map(|window| window.raw.clone())
+ .map(Clipboard::connect)
+ .unwrap_or_else(Clipboard::unconnected);
+ }
- events.push((
- id,
- core::Event::Window(core::window::Event::Closed),
- ));
+ events.push((
+ id,
+ core::Event::Window(core::window::Event::Closed),
+ ));
+ }
}
window::Action::GetOldest(channel) => {
let id =
@@ -1278,7 +1303,7 @@ fn run_action<P, C>(
}
}
window::Action::GetPosition(id, channel) => {
- if let Some(window) = window_manager.get_mut(id) {
+ if let Some(window) = window_manager.get(id) {
let position = window
.raw
.inner_position()
@@ -1293,6 +1318,13 @@ fn run_action<P, C>(
let _ = channel.send(position);
}
}
+ window::Action::GetScaleFactor(id, channel) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let scale_factor = window.raw.scale_factor();
+
+ let _ = channel.send(scale_factor as f32);
+ }
+ }
window::Action::Move(id, position) => {
if let Some(window) = window_manager.get_mut(id) {
window.raw.set_outer_position(
@@ -1403,6 +1435,16 @@ fn run_action<P, C>(
));
}
}
+ window::Action::EnableMousePassthrough(id) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let _ = window.raw.set_cursor_hittest(false);
+ }
+ }
+ window::Action::DisableMousePassthrough(id) => {
+ if let Some(window) = window_manager.get_mut(id) {
+ let _ = window.raw.set_cursor_hittest(true);
+ }
+ }
},
Action::System(action) => match action {
system::Action::QueryInformation(_channel) => {
diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs
index fcbf79f6..3d22e155 100644
--- a/winit/src/program/window_manager.rs
+++ b/winit/src/program/window_manager.rs
@@ -74,12 +74,20 @@ where
self.entries.is_empty()
}
+ pub fn first(&self) -> Option<&Window<P, C>> {
+ self.entries.first_key_value().map(|(_id, window)| window)
+ }
+
pub fn iter_mut(
&mut self,
) -> impl Iterator<Item = (Id, &mut Window<P, C>)> {
self.entries.iter_mut().map(|(k, v)| (*k, v))
}
+ pub fn get(&self, id: Id) -> Option<&Window<P, C>> {
+ self.entries.get(&id)
+ }
+
pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<P, C>> {
self.entries.get_mut(&id)
}