summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/padding.rs2
-rw-r--r--core/src/text.rs2
-rw-r--r--core/src/widget/operation.rs4
-rw-r--r--core/src/widget/operation/focusable.rs14
-rw-r--r--examples/markdown/src/main.rs18
-rw-r--r--examples/pane_grid/src/main.rs22
-rw-r--r--widget/src/container.rs1
-rw-r--r--widget/src/helpers.rs5
-rw-r--r--widget/src/markdown.rs275
-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/text/rich.rs35
-rw-r--r--winit/src/program.rs29
14 files changed, 600 insertions, 185 deletions
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/text.rs b/core/src/text.rs
index 436fee9a..dc8f5785 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -252,7 +252,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/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/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/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/widget/src/container.rs b/widget/src/container.rs
index 54043ad0..ba315741 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();
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index c3ffea45..1cb02830 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)
}
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/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/text/rich.rs b/widget/src/text/rich.rs
index c6aa1e14..1eb0d296 100644
--- a/widget/src/text/rich.rs
+++ b/widget/src/text/rich.rs
@@ -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,
@@ -39,11 +37,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,
@@ -57,10 +56,10 @@ where
/// 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()
}
}
@@ -154,15 +153,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 +160,7 @@ where
Link: Clone + 'a,
Theme: Catalog,
Renderer: core::text::Renderer,
+ Renderer::Font: 'a,
{
fn default() -> Self {
Self::new()
@@ -221,7 +212,7 @@ where
limits,
self.width,
self.height,
- self.spans.as_ref(),
+ self.spans.as_ref().as_ref(),
self.line_height,
self.size,
self.font,
@@ -250,7 +241,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 +385,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 +420,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;
@@ -509,14 +502,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/winit/src/program.rs b/winit/src/program.rs
index efe8a978..c5c3133d 100644
--- a/winit/src/program.rs
+++ b/winit/src/program.rs
@@ -650,7 +650,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,
@@ -665,7 +665,7 @@ async fn run_instance<P, C>(
let Boot {
mut compositor,
mut clipboard,
- } = boot.try_recv().ok().flatten().expect("Receive boot");
+ } = boot.await.expect("Receive boot");
let mut window_manager = WindowManager::new();
let mut is_window_opening = !is_daemon;
@@ -679,7 +679,18 @@ async fn run_instance<P, C>(
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,
@@ -1212,13 +1223,15 @@ fn run_action<P, C>(
*is_window_opening = true;
}
window::Action::Close(id) => {
- let _ = window_manager.remove(id);
+ let window = window_manager.remove(id);
let _ = ui_caches.remove(&id);
- events.push((
- id,
- core::Event::Window(core::window::Event::Closed),
- ));
+ if window.is_some() {
+ events.push((
+ id,
+ core::Event::Window(core::window::Event::Closed),
+ ));
+ }
}
window::Action::GetOldest(channel) => {
let id =