Merge branch 'master' into feature/test-recorder
This commit is contained in:
commit
26c9dc1709
83 changed files with 2627 additions and 1208 deletions
|
|
@ -688,6 +688,50 @@ pub fn text(theme: &Theme, status: Status) -> Style {
|
|||
}
|
||||
}
|
||||
|
||||
/// A button using background shades.
|
||||
pub fn background(theme: &Theme, status: Status) -> Style {
|
||||
let palette = theme.extended_palette();
|
||||
let base = styled(palette.background.base);
|
||||
|
||||
match status {
|
||||
Status::Active => base,
|
||||
Status::Pressed => Style {
|
||||
background: Some(Background::Color(
|
||||
palette.background.strong.color,
|
||||
)),
|
||||
..base
|
||||
},
|
||||
Status::Hovered => Style {
|
||||
background: Some(Background::Color(palette.background.weak.color)),
|
||||
..base
|
||||
},
|
||||
Status::Disabled => disabled(base),
|
||||
}
|
||||
}
|
||||
|
||||
/// A subtle button using weak background shades.
|
||||
pub fn subtle(theme: &Theme, status: Status) -> Style {
|
||||
let palette = theme.extended_palette();
|
||||
let base = styled(palette.background.weakest);
|
||||
|
||||
match status {
|
||||
Status::Active => base,
|
||||
Status::Pressed => Style {
|
||||
background: Some(Background::Color(
|
||||
palette.background.strong.color,
|
||||
)),
|
||||
..base
|
||||
},
|
||||
Status::Hovered => Style {
|
||||
background: Some(Background::Color(
|
||||
palette.background.weaker.color,
|
||||
)),
|
||||
..base
|
||||
},
|
||||
Status::Disabled => disabled(base),
|
||||
}
|
||||
}
|
||||
|
||||
fn styled(pair: palette::Pair) -> Style {
|
||||
Style {
|
||||
background: Some(Background::Color(pair.color)),
|
||||
|
|
|
|||
|
|
@ -320,11 +320,9 @@ where
|
|||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
let mouse_over = cursor.is_over(layout.bounds());
|
||||
|
||||
if mouse_over {
|
||||
if let Some(on_toggle) = &self.on_toggle {
|
||||
shell.publish((on_toggle)(!self.is_checked));
|
||||
shell.capture_event();
|
||||
}
|
||||
if mouse_over && let Some(on_toggle) = &self.on_toggle {
|
||||
shell.publish((on_toggle)(!self.is_checked));
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -556,23 +554,23 @@ pub fn primary(theme: &Theme, status: Status) -> Style {
|
|||
|
||||
match status {
|
||||
Status::Active { is_checked } => styled(
|
||||
palette.primary.strong.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.base,
|
||||
palette.primary.base.text,
|
||||
palette.primary.base,
|
||||
is_checked,
|
||||
),
|
||||
Status::Hovered { is_checked } => styled(
|
||||
palette.primary.strong.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.weak,
|
||||
palette.primary.base.text,
|
||||
palette.primary.strong,
|
||||
is_checked,
|
||||
),
|
||||
Status::Disabled { is_checked } => styled(
|
||||
palette.primary.strong.text,
|
||||
palette.background.weak.color,
|
||||
palette.background.weak,
|
||||
palette.background.weaker,
|
||||
palette.primary.base.text,
|
||||
palette.background.strong,
|
||||
is_checked,
|
||||
),
|
||||
|
|
@ -585,23 +583,23 @@ pub fn secondary(theme: &Theme, status: Status) -> Style {
|
|||
|
||||
match status {
|
||||
Status::Active { is_checked } => styled(
|
||||
palette.background.base.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.base,
|
||||
palette.background.base.text,
|
||||
palette.background.strong,
|
||||
is_checked,
|
||||
),
|
||||
Status::Hovered { is_checked } => styled(
|
||||
palette.background.base.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.weak,
|
||||
palette.background.base.text,
|
||||
palette.background.strong,
|
||||
is_checked,
|
||||
),
|
||||
Status::Disabled { is_checked } => styled(
|
||||
palette.background.strong.color,
|
||||
palette.background.weak.color,
|
||||
palette.background.weak,
|
||||
palette.background.base.text,
|
||||
palette.background.weak,
|
||||
is_checked,
|
||||
),
|
||||
|
|
@ -614,23 +612,23 @@ pub fn success(theme: &Theme, status: Status) -> Style {
|
|||
|
||||
match status {
|
||||
Status::Active { is_checked } => styled(
|
||||
palette.success.base.text,
|
||||
palette.background.weak.color,
|
||||
palette.background.base,
|
||||
palette.success.base.text,
|
||||
palette.success.base,
|
||||
is_checked,
|
||||
),
|
||||
Status::Hovered { is_checked } => styled(
|
||||
palette.success.base.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.weak,
|
||||
palette.success.base.text,
|
||||
palette.success.strong,
|
||||
is_checked,
|
||||
),
|
||||
Status::Disabled { is_checked } => styled(
|
||||
palette.success.base.text,
|
||||
palette.background.weak.color,
|
||||
palette.background.weak,
|
||||
palette.success.base.text,
|
||||
palette.success.weak,
|
||||
is_checked,
|
||||
),
|
||||
|
|
@ -643,23 +641,23 @@ pub fn danger(theme: &Theme, status: Status) -> Style {
|
|||
|
||||
match status {
|
||||
Status::Active { is_checked } => styled(
|
||||
palette.danger.base.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.base,
|
||||
palette.danger.base.text,
|
||||
palette.danger.base,
|
||||
is_checked,
|
||||
),
|
||||
Status::Hovered { is_checked } => styled(
|
||||
palette.danger.base.text,
|
||||
palette.background.strongest.color,
|
||||
palette.background.strong.color,
|
||||
palette.background.weak,
|
||||
palette.danger.base.text,
|
||||
palette.danger.strong,
|
||||
is_checked,
|
||||
),
|
||||
Status::Disabled { is_checked } => styled(
|
||||
palette.danger.base.text,
|
||||
palette.background.weak.color,
|
||||
palette.background.weak,
|
||||
palette.danger.base.text,
|
||||
palette.danger.weak,
|
||||
is_checked,
|
||||
),
|
||||
|
|
@ -667,27 +665,25 @@ pub fn danger(theme: &Theme, status: Status) -> Style {
|
|||
}
|
||||
|
||||
fn styled(
|
||||
icon_color: Color,
|
||||
border_color: Color,
|
||||
base: palette::Pair,
|
||||
icon_color: Color,
|
||||
accent: palette::Pair,
|
||||
is_checked: bool,
|
||||
) -> Style {
|
||||
let (background, border) = if is_checked {
|
||||
(accent, accent.color)
|
||||
} else {
|
||||
(base, border_color)
|
||||
};
|
||||
|
||||
Style {
|
||||
background: Background::Color(if is_checked {
|
||||
accent.color
|
||||
} else {
|
||||
base.color
|
||||
}),
|
||||
background: Background::Color(background.color),
|
||||
icon_color,
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 1.0,
|
||||
color: if is_checked {
|
||||
accent.color
|
||||
} else {
|
||||
border_color
|
||||
},
|
||||
color: border,
|
||||
},
|
||||
text_color: None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,23 +145,13 @@ where
|
|||
let child = child.into();
|
||||
let child_size = child.as_widget().size_hint();
|
||||
|
||||
self.width = self.width.enclose(child_size.width);
|
||||
self.height = self.height.enclose(child_size.height);
|
||||
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an element to the [`Column`], if `Some`.
|
||||
pub fn push_maybe(
|
||||
self,
|
||||
child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
|
||||
) -> Self {
|
||||
if let Some(child) = child {
|
||||
self.push(child)
|
||||
} else {
|
||||
self
|
||||
if !child_size.is_void() {
|
||||
self.width = self.width.enclose(child_size.width);
|
||||
self.height = self.height.enclose(child_size.height);
|
||||
self.children.push(child);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the [`Column`] with the given children.
|
||||
|
|
|
|||
|
|
@ -602,17 +602,16 @@ where
|
|||
|
||||
if is_focused {
|
||||
self.state.with_inner(|state| {
|
||||
if !started_focused {
|
||||
if let Some(on_option_hovered) = &mut self.on_option_hovered
|
||||
{
|
||||
let hovered_option = menu.hovered_option.unwrap_or(0);
|
||||
if !started_focused
|
||||
&& let Some(on_option_hovered) = &mut self.on_option_hovered
|
||||
{
|
||||
let hovered_option = menu.hovered_option.unwrap_or(0);
|
||||
|
||||
if let Some(option) =
|
||||
state.filtered_options.options.get(hovered_option)
|
||||
{
|
||||
shell.publish(on_option_hovered(option.clone()));
|
||||
published_message_to_shell = true;
|
||||
}
|
||||
if let Some(option) =
|
||||
state.filtered_options.options.get(hovered_option)
|
||||
{
|
||||
shell.publish(on_option_hovered(option.clone()));
|
||||
published_message_to_shell = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -625,12 +624,11 @@ where
|
|||
let shift_modifier = modifiers.shift();
|
||||
match (named_key, shift_modifier) {
|
||||
(key::Named::Enter, _) => {
|
||||
if let Some(index) = &menu.hovered_option {
|
||||
if let Some(option) =
|
||||
if let Some(index) = &menu.hovered_option
|
||||
&& let Some(option) =
|
||||
state.filtered_options.options.get(*index)
|
||||
{
|
||||
menu.new_selection = Some(option.clone());
|
||||
}
|
||||
{
|
||||
menu.new_selection = Some(option.clone());
|
||||
}
|
||||
|
||||
shell.capture_event();
|
||||
|
|
@ -653,21 +651,19 @@ where
|
|||
|
||||
if let Some(on_option_hovered) =
|
||||
&mut self.on_option_hovered
|
||||
{
|
||||
if let Some(option) =
|
||||
&& let Some(option) =
|
||||
menu.hovered_option.and_then(|index| {
|
||||
state
|
||||
.filtered_options
|
||||
.options
|
||||
.get(index)
|
||||
})
|
||||
{
|
||||
// Notify the selection
|
||||
shell.publish((on_option_hovered)(
|
||||
option.clone(),
|
||||
));
|
||||
published_message_to_shell = true;
|
||||
}
|
||||
{
|
||||
// Notify the selection
|
||||
shell.publish((on_option_hovered)(
|
||||
option.clone(),
|
||||
));
|
||||
published_message_to_shell = true;
|
||||
}
|
||||
|
||||
shell.capture_event();
|
||||
|
|
@ -701,21 +697,19 @@ where
|
|||
|
||||
if let Some(on_option_hovered) =
|
||||
&mut self.on_option_hovered
|
||||
{
|
||||
if let Some(option) =
|
||||
&& let Some(option) =
|
||||
menu.hovered_option.and_then(|index| {
|
||||
state
|
||||
.filtered_options
|
||||
.options
|
||||
.get(index)
|
||||
})
|
||||
{
|
||||
// Notify the selection
|
||||
shell.publish((on_option_hovered)(
|
||||
option.clone(),
|
||||
));
|
||||
published_message_to_shell = true;
|
||||
}
|
||||
{
|
||||
// Notify the selection
|
||||
shell.publish((on_option_hovered)(
|
||||
option.clone(),
|
||||
));
|
||||
published_message_to_shell = true;
|
||||
}
|
||||
|
||||
shell.capture_event();
|
||||
|
|
|
|||
|
|
@ -714,7 +714,7 @@ pub fn bordered_box(theme: &Theme) -> Style {
|
|||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
color: palette.background.strong.color,
|
||||
color: palette.background.weak.color,
|
||||
},
|
||||
..Style::default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@ use crate::text_input::{self, TextInput};
|
|||
use crate::toggler::{self, Toggler};
|
||||
use crate::tooltip::{self, Tooltip};
|
||||
use crate::vertical_slider::{self, VerticalSlider};
|
||||
use crate::{Column, Grid, MouseArea, Pin, Pop, Row, Space, Stack, Themer};
|
||||
use crate::{Column, Grid, MouseArea, Pin, Row, Sensor, Space, Stack, Themer};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub use crate::table::table;
|
||||
|
||||
/// Creates a [`Column`] with the given children.
|
||||
///
|
||||
/// Columns distribute their children vertically.
|
||||
|
|
@ -988,17 +990,19 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
/// Creates a new [`Pop`] widget.
|
||||
/// Creates a new [`Sensor`] widget.
|
||||
///
|
||||
/// A [`Sensor`] widget can generate messages when its contents are shown,
|
||||
/// hidden, or resized.
|
||||
///
|
||||
/// A [`Pop`] widget can generate messages when it pops in and out of view.
|
||||
/// It can even notify you with anticipation at a given distance!
|
||||
pub fn pop<'a, Message, Theme, Renderer>(
|
||||
pub fn sensor<'a, Message, Theme, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Pop<'a, (), Message, Theme, Renderer>
|
||||
) -> Sensor<'a, (), Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
Pop::new(content)
|
||||
Sensor::new(content)
|
||||
}
|
||||
|
||||
/// Creates a new [`Scrollable`] with the provided content.
|
||||
|
|
|
|||
|
|
@ -26,13 +26,14 @@ pub mod keyed;
|
|||
pub mod overlay;
|
||||
pub mod pane_grid;
|
||||
pub mod pick_list;
|
||||
pub mod pop;
|
||||
pub mod progress_bar;
|
||||
pub mod radio;
|
||||
pub mod row;
|
||||
pub mod rule;
|
||||
pub mod scrollable;
|
||||
pub mod sensor;
|
||||
pub mod slider;
|
||||
pub mod table;
|
||||
pub mod text;
|
||||
pub mod text_editor;
|
||||
pub mod text_input;
|
||||
|
|
@ -73,8 +74,6 @@ pub use pick_list::PickList;
|
|||
#[doc(no_inline)]
|
||||
pub use pin::Pin;
|
||||
#[doc(no_inline)]
|
||||
pub use pop::Pop;
|
||||
#[doc(no_inline)]
|
||||
pub use progress_bar::ProgressBar;
|
||||
#[doc(no_inline)]
|
||||
pub use radio::Radio;
|
||||
|
|
@ -85,6 +84,8 @@ pub use rule::Rule;
|
|||
#[doc(no_inline)]
|
||||
pub use scrollable::Scrollable;
|
||||
#[doc(no_inline)]
|
||||
pub use sensor::Sensor;
|
||||
#[doc(no_inline)]
|
||||
pub use slider::Slider;
|
||||
#[doc(no_inline)]
|
||||
pub use space::Space;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
use crate::core::alignment;
|
||||
use crate::core::border;
|
||||
use crate::core::font::{self, Font};
|
||||
use crate::core::padding;
|
||||
|
|
@ -107,13 +108,17 @@ impl Content {
|
|||
let mut leftover = std::mem::take(&mut self.state.leftover);
|
||||
leftover.push_str(markdown);
|
||||
|
||||
let input = if leftover.trim_end().ends_with('|') {
|
||||
leftover.trim_end().trim_end_matches('|')
|
||||
} else {
|
||||
leftover.as_str()
|
||||
};
|
||||
|
||||
// Pop the last item
|
||||
let _ = self.items.pop();
|
||||
|
||||
// Re-parse last item and new text
|
||||
for (item, source, broken_links) in
|
||||
parse_with(&mut self.state, &leftover)
|
||||
{
|
||||
for (item, source, broken_links) in parse_with(&mut self.state, input) {
|
||||
if !broken_links.is_empty() {
|
||||
let _ = self.incomplete.insert(
|
||||
self.items.len(),
|
||||
|
|
@ -127,6 +132,8 @@ impl Content {
|
|||
self.items.push(item);
|
||||
}
|
||||
|
||||
self.state.leftover.push_str(&leftover[input.len()..]);
|
||||
|
||||
// Re-parse incomplete sections if new references are available
|
||||
if !self.incomplete.is_empty() {
|
||||
self.incomplete.retain(|index, section| {
|
||||
|
|
@ -215,6 +222,29 @@ pub enum Item {
|
|||
Quote(Vec<Item>),
|
||||
/// A horizontal separator.
|
||||
Rule,
|
||||
/// A table.
|
||||
Table {
|
||||
/// The columns of the table.
|
||||
columns: Vec<Column>,
|
||||
/// The rows of the table.
|
||||
rows: Vec<Row>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The column of a table.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Column {
|
||||
/// The header of the column.
|
||||
pub header: Vec<Item>,
|
||||
/// The alignment of the column.
|
||||
pub alignment: pulldown_cmark::Alignment,
|
||||
}
|
||||
|
||||
/// The row of a table.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Row {
|
||||
/// The cells of the row.
|
||||
cells: Vec<Vec<Item>>,
|
||||
}
|
||||
|
||||
/// A bunch of parsed Markdown text.
|
||||
|
|
@ -462,6 +492,12 @@ fn parse_with<'a>(
|
|||
enum Scope {
|
||||
List(List),
|
||||
Quote(Vec<Item>),
|
||||
Table {
|
||||
alignment: Vec<pulldown_cmark::Alignment>,
|
||||
columns: Vec<Column>,
|
||||
rows: Vec<Row>,
|
||||
current: Vec<Item>,
|
||||
},
|
||||
}
|
||||
|
||||
struct List {
|
||||
|
|
@ -479,7 +515,6 @@ fn parse_with<'a>(
|
|||
let mut emphasis = false;
|
||||
let mut strikethrough = false;
|
||||
let mut metadata = false;
|
||||
let mut table = false;
|
||||
let mut code_block = false;
|
||||
let mut link = None;
|
||||
let mut image = None;
|
||||
|
|
@ -535,6 +570,9 @@ fn parse_with<'a>(
|
|||
Scope::Quote(items) => {
|
||||
items.push(item);
|
||||
}
|
||||
Scope::Table { current, .. } => {
|
||||
current.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
|
|
@ -555,21 +593,19 @@ fn parse_with<'a>(
|
|||
#[allow(clippy::drain_collect)]
|
||||
parser.filter_map(move |(event, source)| match event {
|
||||
pulldown_cmark::Event::Start(tag) => match tag {
|
||||
pulldown_cmark::Tag::Strong if !metadata && !table => {
|
||||
pulldown_cmark::Tag::Strong if !metadata => {
|
||||
strong = true;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::Emphasis if !metadata && !table => {
|
||||
pulldown_cmark::Tag::Emphasis if !metadata => {
|
||||
emphasis = true;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::Strikethrough if !metadata && !table => {
|
||||
pulldown_cmark::Tag::Strikethrough if !metadata => {
|
||||
strikethrough = true;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::Link { dest_url, .. }
|
||||
if !metadata && !table =>
|
||||
{
|
||||
pulldown_cmark::Tag::Link { dest_url, .. } if !metadata => {
|
||||
match Url::parse(&dest_url) {
|
||||
Ok(url)
|
||||
if url.scheme() == "http"
|
||||
|
|
@ -584,13 +620,13 @@ fn parse_with<'a>(
|
|||
}
|
||||
pulldown_cmark::Tag::Image {
|
||||
dest_url, title, ..
|
||||
} if !metadata && !table => {
|
||||
} if !metadata => {
|
||||
image = Url::parse(&dest_url)
|
||||
.ok()
|
||||
.map(|url| (url, title.into_string()));
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::List(first_item) if !metadata && !table => {
|
||||
pulldown_cmark::Tag::List(first_item) if !metadata => {
|
||||
let prev = if spans.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -616,7 +652,7 @@ fn parse_with<'a>(
|
|||
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::BlockQuote(_kind) if !metadata && !table => {
|
||||
pulldown_cmark::Tag::BlockQuote(_kind) if !metadata => {
|
||||
let prev = if spans.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -634,7 +670,7 @@ fn parse_with<'a>(
|
|||
}
|
||||
pulldown_cmark::Tag::CodeBlock(
|
||||
pulldown_cmark::CodeBlockKind::Fenced(language),
|
||||
) if !metadata && !table => {
|
||||
) if !metadata => {
|
||||
#[cfg(feature = "highlighter")]
|
||||
{
|
||||
highlighter = Some({
|
||||
|
|
@ -672,38 +708,54 @@ fn parse_with<'a>(
|
|||
metadata = true;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::Table(_) => {
|
||||
table = true;
|
||||
pulldown_cmark::Tag::Table(alignment) => {
|
||||
stack.push(Scope::Table {
|
||||
columns: Vec::with_capacity(alignment.len()),
|
||||
alignment,
|
||||
current: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::TableHead => {
|
||||
strong = true;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Tag::TableRow => {
|
||||
let Scope::Table { rows, .. } = stack.last_mut()? else {
|
||||
return None;
|
||||
};
|
||||
|
||||
rows.push(Row { cells: Vec::new() });
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
pulldown_cmark::Event::End(tag) => match tag {
|
||||
pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => {
|
||||
produce(
|
||||
state.borrow_mut(),
|
||||
&mut stack,
|
||||
Item::Heading(level, Text::new(spans.drain(..).collect())),
|
||||
source,
|
||||
)
|
||||
}
|
||||
pulldown_cmark::TagEnd::Strong if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Heading(level) if !metadata => produce(
|
||||
state.borrow_mut(),
|
||||
&mut stack,
|
||||
Item::Heading(level, Text::new(spans.drain(..).collect())),
|
||||
source,
|
||||
),
|
||||
pulldown_cmark::TagEnd::Strong if !metadata => {
|
||||
strong = false;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::TagEnd::Emphasis if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Emphasis if !metadata => {
|
||||
emphasis = false;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::TagEnd::Strikethrough if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Strikethrough if !metadata => {
|
||||
strikethrough = false;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::TagEnd::Link if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Link if !metadata => {
|
||||
link = None;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::TagEnd::Paragraph if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Paragraph if !metadata => {
|
||||
if spans.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -715,7 +767,7 @@ fn parse_with<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
pulldown_cmark::TagEnd::Item if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Item if !metadata => {
|
||||
if spans.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -727,7 +779,7 @@ fn parse_with<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
pulldown_cmark::TagEnd::List(_) if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::List(_) if !metadata => {
|
||||
let scope = stack.pop()?;
|
||||
|
||||
let Scope::List(list) = scope else {
|
||||
|
|
@ -744,9 +796,7 @@ fn parse_with<'a>(
|
|||
source,
|
||||
)
|
||||
}
|
||||
pulldown_cmark::TagEnd::BlockQuote(_kind)
|
||||
if !metadata && !table =>
|
||||
{
|
||||
pulldown_cmark::TagEnd::BlockQuote(_kind) if !metadata => {
|
||||
let scope = stack.pop()?;
|
||||
|
||||
let Scope::Quote(quote) = scope else {
|
||||
|
|
@ -760,7 +810,7 @@ fn parse_with<'a>(
|
|||
source,
|
||||
)
|
||||
}
|
||||
pulldown_cmark::TagEnd::Image if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::Image if !metadata => {
|
||||
let (url, title) = image.take()?;
|
||||
let alt = Text::new(spans.drain(..).collect());
|
||||
|
||||
|
|
@ -774,7 +824,7 @@ fn parse_with<'a>(
|
|||
source,
|
||||
)
|
||||
}
|
||||
pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => {
|
||||
pulldown_cmark::TagEnd::CodeBlock if !metadata => {
|
||||
code_block = false;
|
||||
|
||||
#[cfg(feature = "highlighter")]
|
||||
|
|
@ -798,12 +848,60 @@ fn parse_with<'a>(
|
|||
None
|
||||
}
|
||||
pulldown_cmark::TagEnd::Table => {
|
||||
table = false;
|
||||
let scope = stack.pop()?;
|
||||
|
||||
let Scope::Table { columns, rows, .. } = scope else {
|
||||
return None;
|
||||
};
|
||||
|
||||
produce(
|
||||
state.borrow_mut(),
|
||||
&mut stack,
|
||||
Item::Table { columns, rows },
|
||||
source,
|
||||
)
|
||||
}
|
||||
pulldown_cmark::TagEnd::TableHead => {
|
||||
strong = false;
|
||||
None
|
||||
}
|
||||
pulldown_cmark::TagEnd::TableCell => {
|
||||
if !spans.is_empty() {
|
||||
let _ = produce(
|
||||
state.borrow_mut(),
|
||||
&mut stack,
|
||||
Item::Paragraph(Text::new(spans.drain(..).collect())),
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
let Scope::Table {
|
||||
alignment,
|
||||
columns,
|
||||
rows,
|
||||
current,
|
||||
} = stack.last_mut()?
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if columns.len() < alignment.len() {
|
||||
columns.push(Column {
|
||||
header: std::mem::take(current),
|
||||
alignment: alignment[columns.len()],
|
||||
});
|
||||
} else {
|
||||
rows.last_mut()
|
||||
.expect("table row")
|
||||
.cells
|
||||
.push(std::mem::take(current));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
pulldown_cmark::Event::Text(text) if !metadata && !table => {
|
||||
pulldown_cmark::Event::Text(text) if !metadata => {
|
||||
if code_block {
|
||||
code.push_str(&text);
|
||||
|
||||
|
|
@ -844,7 +942,7 @@ fn parse_with<'a>(
|
|||
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Event::Code(code) if !metadata && !table => {
|
||||
pulldown_cmark::Event::Code(code) if !metadata => {
|
||||
let span = Span::Standard {
|
||||
text: code.into_string(),
|
||||
strong,
|
||||
|
|
@ -857,7 +955,7 @@ fn parse_with<'a>(
|
|||
spans.push(span);
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Event::SoftBreak if !metadata && !table => {
|
||||
pulldown_cmark::Event::SoftBreak if !metadata => {
|
||||
spans.push(Span::Standard {
|
||||
text: String::from(" "),
|
||||
strikethrough,
|
||||
|
|
@ -868,7 +966,7 @@ fn parse_with<'a>(
|
|||
});
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Event::HardBreak if !metadata && !table => {
|
||||
pulldown_cmark::Event::HardBreak if !metadata => {
|
||||
spans.push(Span::Standard {
|
||||
text: String::from("\n"),
|
||||
strikethrough,
|
||||
|
|
@ -974,8 +1072,8 @@ impl Style {
|
|||
Self {
|
||||
inline_code_padding: padding::left(1).right(1),
|
||||
inline_code_highlight: Highlight {
|
||||
background: color!(0x111).into(),
|
||||
border: border::rounded(2),
|
||||
background: color!(0x111111).into(),
|
||||
border: border::rounded(4),
|
||||
},
|
||||
inline_code_color: Color::WHITE,
|
||||
link_color: palette.primary,
|
||||
|
|
@ -1113,6 +1211,7 @@ where
|
|||
} => viewer.ordered_list(settings, *start, items),
|
||||
Item::Quote(quote) => viewer.quote(settings, quote),
|
||||
Item::Rule => viewer.rule(settings),
|
||||
Item::Table { columns, rows } => viewer.table(settings, columns, rows),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1222,9 +1321,14 @@ where
|
|||
Theme: Catalog + 'a,
|
||||
Renderer: core::text::Renderer<Font = Font> + 'a,
|
||||
{
|
||||
let digits = ((start + items.len() as u64).max(1) as f32).log10().ceil();
|
||||
|
||||
column(items.iter().enumerate().map(|(i, items)| {
|
||||
row![
|
||||
text!("{}.", i as u64 + start).size(settings.text_size),
|
||||
text!("{}.", i as u64 + start)
|
||||
.size(settings.text_size)
|
||||
.align_x(alignment::Horizontal::Right)
|
||||
.width(settings.text_size * ((digits / 2.0).ceil() + 1.0)),
|
||||
view_with(
|
||||
items,
|
||||
Settings {
|
||||
|
|
@ -1238,7 +1342,6 @@ where
|
|||
.into()
|
||||
}))
|
||||
.spacing(settings.spacing * 0.75)
|
||||
.padding([0.0, settings.spacing.0])
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -1313,6 +1416,80 @@ where
|
|||
horizontal_rule(2).into()
|
||||
}
|
||||
|
||||
/// Displays a table using the default look.
|
||||
pub fn table<'a, Message, Theme, Renderer>(
|
||||
viewer: &impl Viewer<'a, Message, Theme, Renderer>,
|
||||
settings: Settings,
|
||||
columns: &'a [Column],
|
||||
rows: &'a [Row],
|
||||
) -> Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: core::text::Renderer<Font = Font> + 'a,
|
||||
{
|
||||
use crate::table;
|
||||
|
||||
let table = table(
|
||||
columns.iter().enumerate().map(move |(i, column)| {
|
||||
table::column(
|
||||
items(viewer, settings, &column.header),
|
||||
move |row: &Row| {
|
||||
if let Some(cells) = row.cells.get(i) {
|
||||
items(viewer, settings, cells)
|
||||
} else {
|
||||
text("").into()
|
||||
}
|
||||
},
|
||||
)
|
||||
.align_x(match column.alignment {
|
||||
pulldown_cmark::Alignment::None
|
||||
| pulldown_cmark::Alignment::Left => {
|
||||
alignment::Horizontal::Left
|
||||
}
|
||||
pulldown_cmark::Alignment::Center => {
|
||||
alignment::Horizontal::Center
|
||||
}
|
||||
pulldown_cmark::Alignment::Right => {
|
||||
alignment::Horizontal::Right
|
||||
}
|
||||
})
|
||||
}),
|
||||
rows,
|
||||
)
|
||||
.padding_x(settings.spacing.0)
|
||||
.padding_y(settings.spacing.0 / 2.0)
|
||||
.separator_x(0);
|
||||
|
||||
scrollable(table)
|
||||
.direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::default(),
|
||||
))
|
||||
.spacing(settings.spacing.0 / 2.0)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Displays a column of items with the default look.
|
||||
pub fn items<'a, Message, Theme, Renderer>(
|
||||
viewer: &impl Viewer<'a, Message, Theme, Renderer>,
|
||||
settings: Settings,
|
||||
items: &'a [Item],
|
||||
) -> Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: core::text::Renderer<Font = Font> + 'a,
|
||||
{
|
||||
column(
|
||||
items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, content)| item(viewer, settings, content, i)),
|
||||
)
|
||||
.spacing(settings.spacing.0)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A view strategy to display a Markdown [`Item`].
|
||||
pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
|
||||
where
|
||||
|
|
@ -1429,6 +1606,18 @@ where
|
|||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
rule()
|
||||
}
|
||||
|
||||
/// Displays a table.
|
||||
///
|
||||
/// By default, it calls [`table`].
|
||||
fn table(
|
||||
&self,
|
||||
settings: Settings,
|
||||
columns: &'a [Column],
|
||||
rows: &'a [Row],
|
||||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
table(self, settings, columns, rows)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -1446,7 +1635,11 @@ where
|
|||
|
||||
/// The theme catalog of Markdown items.
|
||||
pub trait Catalog:
|
||||
container::Catalog + scrollable::Catalog + rule::Catalog + text::Catalog
|
||||
container::Catalog
|
||||
+ scrollable::Catalog
|
||||
+ rule::Catalog
|
||||
+ text::Catalog
|
||||
+ crate::table::Catalog
|
||||
{
|
||||
/// The styling class of a Markdown code block.
|
||||
fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;
|
||||
|
|
|
|||
|
|
@ -377,24 +377,24 @@ fn update<Message: Clone, Theme, Renderer>(
|
|||
shell.capture_event();
|
||||
}
|
||||
|
||||
if let Some(position) = cursor_position {
|
||||
if let Some(message) = widget.on_double_click.as_ref() {
|
||||
let new_click = mouse::Click::new(
|
||||
position,
|
||||
mouse::Button::Left,
|
||||
state.previous_click,
|
||||
);
|
||||
if let Some(position) = cursor_position
|
||||
&& let Some(message) = widget.on_double_click.as_ref()
|
||||
{
|
||||
let new_click = mouse::Click::new(
|
||||
position,
|
||||
mouse::Button::Left,
|
||||
state.previous_click,
|
||||
);
|
||||
|
||||
if new_click.kind() == mouse::click::Kind::Double {
|
||||
shell.publish(message.clone());
|
||||
}
|
||||
|
||||
state.previous_click = Some(new_click);
|
||||
|
||||
// Even if this is not a double click, but the press is nevertheless
|
||||
// processed by us and should not be popup to parent widgets.
|
||||
shell.capture_event();
|
||||
if new_click.kind() == mouse::click::Kind::Double {
|
||||
shell.publish(message.clone());
|
||||
}
|
||||
|
||||
state.previous_click = Some(new_click);
|
||||
|
||||
// Even if this is not a double click, but the press is nevertheless
|
||||
// processed by us and should not be popup to parent widgets.
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
|
|
|
|||
|
|
@ -407,13 +407,12 @@ where
|
|||
) {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
if cursor.is_over(layout.bounds()) {
|
||||
if let Some(index) = *self.hovered_option {
|
||||
if let Some(option) = self.options.get(index) {
|
||||
shell.publish((self.on_selected)(option.clone()));
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
if cursor.is_over(layout.bounds())
|
||||
&& let Some(index) = *self.hovered_option
|
||||
&& let Some(option) = self.options.get(index)
|
||||
{
|
||||
shell.publish((self.on_selected)(option.clone()));
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||
|
|
@ -431,19 +430,16 @@ where
|
|||
let new_hovered_option =
|
||||
(cursor_position.y / option_height) as usize;
|
||||
|
||||
if *self.hovered_option != Some(new_hovered_option) {
|
||||
if let Some(option) =
|
||||
if *self.hovered_option != Some(new_hovered_option)
|
||||
&& let Some(option) =
|
||||
self.options.get(new_hovered_option)
|
||||
{
|
||||
if let Some(on_option_hovered) = self.on_option_hovered
|
||||
{
|
||||
if let Some(on_option_hovered) =
|
||||
self.on_option_hovered
|
||||
{
|
||||
shell
|
||||
.publish(on_option_hovered(option.clone()));
|
||||
}
|
||||
|
||||
shell.request_redraw();
|
||||
shell.publish(on_option_hovered(option.clone()));
|
||||
}
|
||||
|
||||
shell.request_redraw();
|
||||
}
|
||||
|
||||
*self.hovered_option = Some(new_hovered_option);
|
||||
|
|
@ -464,11 +460,11 @@ where
|
|||
*self.hovered_option =
|
||||
Some((cursor_position.y / option_height) as usize);
|
||||
|
||||
if let Some(index) = *self.hovered_option {
|
||||
if let Some(option) = self.options.get(index) {
|
||||
shell.publish((self.on_selected)(option.clone()));
|
||||
shell.capture_event();
|
||||
}
|
||||
if let Some(index) = *self.hovered_option
|
||||
&& let Some(option) = self.options.get(index)
|
||||
{
|
||||
shell.publish((self.on_selected)(option.clone()));
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -593,56 +593,47 @@ where
|
|||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. })
|
||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||
if let Some((pane, origin)) = action.picked_pane() {
|
||||
if let Some(on_drag) = on_drag {
|
||||
if let Some(cursor_position) = cursor.position() {
|
||||
if cursor_position.distance(origin)
|
||||
> DRAG_DEADBAND_DISTANCE
|
||||
{
|
||||
let event = if let Some(edge) =
|
||||
in_edge(layout, cursor_position)
|
||||
if let Some((pane, origin)) = action.picked_pane()
|
||||
&& let Some(on_drag) = on_drag
|
||||
&& let Some(cursor_position) = cursor.position()
|
||||
{
|
||||
if cursor_position.distance(origin) > DRAG_DEADBAND_DISTANCE
|
||||
{
|
||||
let event = if let Some(edge) =
|
||||
in_edge(layout, cursor_position)
|
||||
{
|
||||
DragEvent::Dropped {
|
||||
pane,
|
||||
target: Target::Edge(edge),
|
||||
}
|
||||
} else {
|
||||
let dropped_region = self
|
||||
.panes
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(&self.contents)
|
||||
.zip(layout.children())
|
||||
.find_map(|(target, layout)| {
|
||||
layout_region(layout, cursor_position)
|
||||
.map(|region| (target, region))
|
||||
});
|
||||
|
||||
match dropped_region {
|
||||
Some(((target, _), region))
|
||||
if pane != target =>
|
||||
{
|
||||
DragEvent::Dropped {
|
||||
pane,
|
||||
target: Target::Edge(edge),
|
||||
target: Target::Pane(target, region),
|
||||
}
|
||||
} else {
|
||||
let dropped_region = self
|
||||
.panes
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(&self.contents)
|
||||
.zip(layout.children())
|
||||
.find_map(|(target, layout)| {
|
||||
layout_region(
|
||||
layout,
|
||||
cursor_position,
|
||||
)
|
||||
.map(|region| (target, region))
|
||||
});
|
||||
|
||||
match dropped_region {
|
||||
Some(((target, _), region))
|
||||
if pane != target =>
|
||||
{
|
||||
DragEvent::Dropped {
|
||||
pane,
|
||||
target: Target::Pane(
|
||||
target, region,
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => DragEvent::Canceled { pane },
|
||||
}
|
||||
};
|
||||
|
||||
shell.publish(on_drag(event));
|
||||
} else {
|
||||
shell.publish(on_drag(DragEvent::Canceled {
|
||||
pane,
|
||||
}));
|
||||
}
|
||||
_ => DragEvent::Canceled { pane },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shell.publish(on_drag(event));
|
||||
} else {
|
||||
shell.publish(on_drag(DragEvent::Canceled { pane }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -660,34 +651,33 @@ where
|
|||
bounds.size(),
|
||||
);
|
||||
|
||||
if let Some((axis, rectangle, _)) = splits.get(&split) {
|
||||
if let Some(cursor_position) = cursor.position() {
|
||||
let ratio = match axis {
|
||||
Axis::Horizontal => {
|
||||
let position = cursor_position.y
|
||||
- bounds.y
|
||||
- rectangle.y;
|
||||
if let Some((axis, rectangle, _)) = splits.get(&split)
|
||||
&& let Some(cursor_position) = cursor.position()
|
||||
{
|
||||
let ratio = match axis {
|
||||
Axis::Horizontal => {
|
||||
let position = cursor_position.y
|
||||
- bounds.y
|
||||
- rectangle.y;
|
||||
|
||||
(position / rectangle.height)
|
||||
.clamp(0.0, 1.0)
|
||||
}
|
||||
Axis::Vertical => {
|
||||
let position = cursor_position.x
|
||||
- bounds.x
|
||||
- rectangle.x;
|
||||
(position / rectangle.height)
|
||||
.clamp(0.0, 1.0)
|
||||
}
|
||||
Axis::Vertical => {
|
||||
let position = cursor_position.x
|
||||
- bounds.x
|
||||
- rectangle.x;
|
||||
|
||||
(position / rectangle.width)
|
||||
.clamp(0.0, 1.0)
|
||||
}
|
||||
};
|
||||
(position / rectangle.width).clamp(0.0, 1.0)
|
||||
}
|
||||
};
|
||||
|
||||
shell.publish(on_resize(ResizeEvent {
|
||||
split,
|
||||
ratio,
|
||||
}));
|
||||
shell.publish(on_resize(ResizeEvent {
|
||||
split,
|
||||
ratio,
|
||||
}));
|
||||
|
||||
shell.capture_event();
|
||||
}
|
||||
shell.capture_event();
|
||||
}
|
||||
} else if action.picked_pane().is_some() {
|
||||
shell.request_redraw();
|
||||
|
|
@ -889,24 +879,23 @@ where
|
|||
viewport,
|
||||
);
|
||||
|
||||
if picked_pane.is_some() && pane_in_edge.is_none() {
|
||||
if let Some(region) =
|
||||
if picked_pane.is_some()
|
||||
&& pane_in_edge.is_none()
|
||||
&& let Some(region) =
|
||||
cursor.position().and_then(|cursor_position| {
|
||||
layout_region(pane_layout, cursor_position)
|
||||
})
|
||||
{
|
||||
let bounds =
|
||||
layout_region_bounds(pane_layout, region);
|
||||
{
|
||||
let bounds = layout_region_bounds(pane_layout, region);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border: style.hovered_region.border,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.hovered_region.background,
|
||||
);
|
||||
}
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border: style.hovered_region.border,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.hovered_region.background,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
|
@ -937,64 +926,62 @@ where
|
|||
}
|
||||
|
||||
// Render picked pane last
|
||||
if let Some(((content, tree), origin, layout)) = render_picked_pane {
|
||||
if let Some(cursor_position) = cursor.position() {
|
||||
let bounds = layout.bounds();
|
||||
if let Some(((content, tree), origin, layout)) = render_picked_pane
|
||||
&& let Some(cursor_position) = cursor.position()
|
||||
{
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let translation =
|
||||
cursor_position - Point::new(origin.x, origin.y);
|
||||
let translation = cursor_position - Point::new(origin.x, origin.y);
|
||||
|
||||
renderer.with_translation(translation, |renderer| {
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
content.draw(
|
||||
tree,
|
||||
renderer,
|
||||
theme,
|
||||
defaults,
|
||||
layout,
|
||||
pane_cursor,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
renderer.with_translation(translation, |renderer| {
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
content.draw(
|
||||
tree,
|
||||
renderer,
|
||||
theme,
|
||||
defaults,
|
||||
layout,
|
||||
pane_cursor,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if picked_pane.is_none() {
|
||||
if let Some((axis, split_region, is_picked)) = picked_split {
|
||||
let highlight = if is_picked {
|
||||
style.picked_split
|
||||
} else {
|
||||
style.hovered_split
|
||||
};
|
||||
if picked_pane.is_none()
|
||||
&& let Some((axis, split_region, is_picked)) = picked_split
|
||||
{
|
||||
let highlight = if is_picked {
|
||||
style.picked_split
|
||||
} else {
|
||||
style.hovered_split
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: match axis {
|
||||
Axis::Horizontal => Rectangle {
|
||||
x: split_region.x,
|
||||
y: (split_region.y
|
||||
+ (split_region.height - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
width: split_region.width,
|
||||
height: highlight.width,
|
||||
},
|
||||
Axis::Vertical => Rectangle {
|
||||
x: (split_region.x
|
||||
+ (split_region.width - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
y: split_region.y,
|
||||
width: highlight.width,
|
||||
height: split_region.height,
|
||||
},
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: match axis {
|
||||
Axis::Horizontal => Rectangle {
|
||||
x: split_region.x,
|
||||
y: (split_region.y
|
||||
+ (split_region.height - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
width: split_region.width,
|
||||
height: highlight.width,
|
||||
},
|
||||
Axis::Vertical => Rectangle {
|
||||
x: (split_region.x
|
||||
+ (split_region.width - highlight.width) / 2.0)
|
||||
.round(),
|
||||
y: split_region.y,
|
||||
width: highlight.width,
|
||||
height: split_region.height,
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
highlight.color,
|
||||
);
|
||||
}
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
highlight.color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1086,15 +1073,15 @@ fn click_pane<'a, Message, T>(
|
|||
shell.publish(on_click(pane));
|
||||
}
|
||||
|
||||
if let Some(on_drag) = &on_drag {
|
||||
if content.can_be_dragged_at(layout, cursor_position) {
|
||||
*action = state::Action::Dragging {
|
||||
pane,
|
||||
origin: cursor_position,
|
||||
};
|
||||
if let Some(on_drag) = &on_drag
|
||||
&& content.can_be_dragged_at(layout, cursor_position)
|
||||
{
|
||||
*action = state::Action::Dragging {
|
||||
pane,
|
||||
origin: cursor_position,
|
||||
};
|
||||
|
||||
shell.publish(on_drag(DragEvent::Picked { pane }));
|
||||
}
|
||||
shell.publish(on_drag(DragEvent::Picked { pane }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,14 +219,14 @@ impl<T> State<T> {
|
|||
pane: Pane,
|
||||
swap: bool,
|
||||
) {
|
||||
if let Some((state, _)) = self.close(pane) {
|
||||
if let Some((new_pane, _)) = self.split(axis, target, state) {
|
||||
// Ensure new node corresponds to original closed `Pane` for state continuity
|
||||
self.relabel(new_pane, pane);
|
||||
if let Some((state, _)) = self.close(pane)
|
||||
&& let Some((new_pane, _)) = self.split(axis, target, state)
|
||||
{
|
||||
// Ensure new node corresponds to original closed `Pane` for state continuity
|
||||
self.relabel(new_pane, pane);
|
||||
|
||||
if swap {
|
||||
self.swap(target, pane);
|
||||
}
|
||||
if swap {
|
||||
self.swap(target, pane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -257,13 +257,12 @@ impl<T> State<T> {
|
|||
pane: Pane,
|
||||
inverse: bool,
|
||||
) {
|
||||
if let Some((state, _)) = self.close(pane) {
|
||||
if let Some((new_pane, _)) =
|
||||
if let Some((state, _)) = self.close(pane)
|
||||
&& let Some((new_pane, _)) =
|
||||
self.split_node(axis, None, state, inverse)
|
||||
{
|
||||
// Ensure new node corresponds to original closed `Pane` for state continuity
|
||||
self.relabel(new_pane, pane);
|
||||
}
|
||||
{
|
||||
// Ensure new node corresponds to original closed `Pane` for state continuity
|
||||
self.relabel(new_pane, pane);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,38 +174,28 @@ where
|
|||
let title_layout = children.next().unwrap();
|
||||
let mut show_title = true;
|
||||
|
||||
if let Some(controls) = &self.controls {
|
||||
if show_controls || self.always_show_controls {
|
||||
let controls_layout = children.next().unwrap();
|
||||
if title_layout.bounds().width + controls_layout.bounds().width
|
||||
> padded.bounds().width
|
||||
{
|
||||
if let Some(compact) = controls.compact.as_ref() {
|
||||
let compact_layout = children.next().unwrap();
|
||||
if let Some(controls) = &self.controls
|
||||
&& (show_controls || self.always_show_controls)
|
||||
{
|
||||
let controls_layout = children.next().unwrap();
|
||||
if title_layout.bounds().width + controls_layout.bounds().width
|
||||
> padded.bounds().width
|
||||
{
|
||||
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,
|
||||
);
|
||||
}
|
||||
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,
|
||||
|
|
@ -216,6 +206,16 @@ where
|
|||
viewport,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
controls.full.as_widget().draw(
|
||||
&tree.children[1],
|
||||
renderer,
|
||||
theme,
|
||||
&inherited_style,
|
||||
controls_layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -899,7 +899,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
let active = Style {
|
||||
text_color: palette.background.weak.text,
|
||||
background: palette.background.weak.color.into(),
|
||||
placeholder_color: palette.background.strong.color,
|
||||
placeholder_color: palette.secondary.base.color,
|
||||
handle_color: palette.background.weak.text,
|
||||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
|
|
|
|||
|
|
@ -136,23 +136,13 @@ where
|
|||
let child = child.into();
|
||||
let child_size = child.as_widget().size_hint();
|
||||
|
||||
self.width = self.width.enclose(child_size.width);
|
||||
self.height = self.height.enclose(child_size.height);
|
||||
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an element to the [`Row`], if `Some`.
|
||||
pub fn push_maybe(
|
||||
self,
|
||||
child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
|
||||
) -> Self {
|
||||
if let Some(child) = child {
|
||||
self.push(child)
|
||||
} else {
|
||||
self
|
||||
if !child_size.is_void() {
|
||||
self.width = self.width.enclose(child_size.width);
|
||||
self.height = self.height.enclose(child_size.height);
|
||||
self.children.push(child);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the [`Row`] with the given children.
|
||||
|
|
@ -170,6 +160,7 @@ where
|
|||
Wrapping {
|
||||
row: self,
|
||||
vertical_spacing: None,
|
||||
align_x: alignment::Horizontal::Left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -378,6 +369,7 @@ pub struct Wrapping<
|
|||
> {
|
||||
row: Row<'a, Message, Theme, Renderer>,
|
||||
vertical_spacing: Option<f32>,
|
||||
align_x: alignment::Horizontal,
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Wrapping<'_, Message, Theme, Renderer> {
|
||||
|
|
@ -386,6 +378,15 @@ impl<Message, Theme, Renderer> Wrapping<'_, Message, Theme, Renderer> {
|
|||
self.vertical_spacing = Some(amount.into().0);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the wrapping [`Row`].
|
||||
pub fn align_x(
|
||||
mut self,
|
||||
align_x: impl Into<alignment::Horizontal>,
|
||||
) -> Self {
|
||||
self.align_x = align_x.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
|
|
@ -433,9 +434,9 @@ where
|
|||
Alignment::End => 1.0,
|
||||
};
|
||||
|
||||
let align = |row_start: std::ops::Range<usize>,
|
||||
row_height: f32,
|
||||
children: &mut Vec<layout::Node>| {
|
||||
let align_y = |row_start: std::ops::Range<usize>,
|
||||
row_height: f32,
|
||||
children: &mut Vec<layout::Node>| {
|
||||
if align_factor != 0.0 {
|
||||
for node in &mut children[row_start] {
|
||||
let height = node.size().height;
|
||||
|
|
@ -460,7 +461,7 @@ where
|
|||
if x != 0.0 && x + child_size.width > max_width {
|
||||
intrinsic_size.width = intrinsic_size.width.max(x - spacing);
|
||||
|
||||
align(row_start..i, row_height, &mut children);
|
||||
align_y(row_start..i, row_height, &mut children);
|
||||
|
||||
y += row_height + vertical_spacing;
|
||||
x = 0.0;
|
||||
|
|
@ -483,7 +484,42 @@ where
|
|||
}
|
||||
|
||||
intrinsic_size.height = y + row_height;
|
||||
align(row_start..children.len(), row_height, &mut children);
|
||||
align_y(row_start..children.len(), row_height, &mut children);
|
||||
|
||||
let align_factor = match self.align_x {
|
||||
alignment::Horizontal::Left => 0.0,
|
||||
alignment::Horizontal::Center => 2.0,
|
||||
alignment::Horizontal::Right => 1.0,
|
||||
};
|
||||
|
||||
if align_factor != 0.0 {
|
||||
let total_width = intrinsic_size.width;
|
||||
|
||||
let mut row_start = 0;
|
||||
|
||||
for i in 0..children.len() {
|
||||
let bounds = children[i].bounds();
|
||||
let row_width = bounds.x + bounds.width;
|
||||
|
||||
let next_x = children
|
||||
.get(i + 1)
|
||||
.map(|node| node.bounds().x)
|
||||
.unwrap_or_default();
|
||||
|
||||
if next_x == 0.0 {
|
||||
let translation = Vector::new(
|
||||
(total_width - row_width) / align_factor,
|
||||
0.0,
|
||||
);
|
||||
|
||||
for node in &mut children[row_start..=i] {
|
||||
node.translate_mut(translation);
|
||||
}
|
||||
|
||||
row_start = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let size =
|
||||
limits.resolve(self.row.width, self.row.height, intrinsic_size);
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ where
|
|||
let style = theme.style(&self.class);
|
||||
|
||||
let bounds = if self.is_horizontal {
|
||||
let line_y = (bounds.y + (bounds.height / 2.0)).round();
|
||||
let line_y = bounds.y.round();
|
||||
|
||||
let (offset, line_width) = style.fill_mode.fill(bounds.width);
|
||||
let line_x = bounds.x + offset;
|
||||
|
|
@ -146,7 +146,7 @@ where
|
|||
height: bounds.height,
|
||||
}
|
||||
} else {
|
||||
let line_x = (bounds.x + (bounds.width / 2.0)).round();
|
||||
let line_x = bounds.x.round();
|
||||
|
||||
let (offset, line_height) = style.fill_mode.fill(bounds.height);
|
||||
let line_y = bounds.y + offset;
|
||||
|
|
@ -300,3 +300,15 @@ pub fn default(theme: &Theme) -> Style {
|
|||
snap: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Rule`] styling using the weak background color.
|
||||
pub fn weak(theme: &Theme) -> Style {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
Style {
|
||||
color: palette.background.weak.color,
|
||||
radius: 0.0.into(),
|
||||
fill_mode: FillMode::Full,
|
||||
snap: true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -798,12 +798,11 @@ where
|
|||
},
|
||||
);
|
||||
|
||||
if !had_input_method {
|
||||
if let InputMethod::Enabled { position, .. } =
|
||||
if !had_input_method
|
||||
&& let InputMethod::Enabled { position, .. } =
|
||||
shell.input_method_mut()
|
||||
{
|
||||
*position = *position - translation;
|
||||
}
|
||||
{
|
||||
*position = *position - translation;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1091,23 +1090,22 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
if let Some(scroller) = scrollbar.scroller {
|
||||
if scroller.bounds.width > 0.0
|
||||
&& scroller.bounds.height > 0.0
|
||||
&& (style.scroller.color != Color::TRANSPARENT
|
||||
|| (style.scroller.border.color
|
||||
!= Color::TRANSPARENT
|
||||
&& style.scroller.border.width > 0.0))
|
||||
{
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: scroller.bounds,
|
||||
border: style.scroller.border,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.scroller.color,
|
||||
);
|
||||
}
|
||||
if let Some(scroller) = scrollbar.scroller
|
||||
&& scroller.bounds.width > 0.0
|
||||
&& scroller.bounds.height > 0.0
|
||||
&& (style.scroller.color != Color::TRANSPARENT
|
||||
|| (style.scroller.border.color
|
||||
!= Color::TRANSPARENT
|
||||
&& style.scroller.border.width > 0.0))
|
||||
{
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: scroller.bounds,
|
||||
border: style.scroller.border,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.scroller.color,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -2069,7 +2067,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
background: Some(palette.background.weak.color.into()),
|
||||
border: border::rounded(2),
|
||||
scroller: Scroller {
|
||||
color: palette.background.strong.color,
|
||||
color: palette.background.strongest.color,
|
||||
border: border::rounded(2),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use crate::core::{
|
|||
///
|
||||
/// It can even notify you with anticipation at a given distance!
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Pop<
|
||||
pub struct Sensor<
|
||||
'a,
|
||||
Key,
|
||||
Message,
|
||||
|
|
@ -32,11 +32,11 @@ pub struct Pop<
|
|||
delay: Duration,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Pop<'a, (), Message, Theme, Renderer>
|
||||
impl<'a, Message, Theme, Renderer> Sensor<'a, (), Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
/// Creates a new [`Pop`] widget with the given content.
|
||||
/// Creates a new [`Sensor`] widget with the given content.
|
||||
pub fn new(
|
||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Self {
|
||||
|
|
@ -52,7 +52,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Key, Message, Theme, Renderer> Pop<'a, Key, Message, Theme, Renderer>
|
||||
impl<'a, Key, Message, Theme, Renderer>
|
||||
Sensor<'a, Key, Message, Theme, Renderer>
|
||||
where
|
||||
Key: self::Key,
|
||||
Renderer: core::Renderer,
|
||||
|
|
@ -82,17 +83,17 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the key of the [`Pop`] widget, for continuity.
|
||||
/// Sets the key of the [`Sensor`] widget, for continuity.
|
||||
///
|
||||
/// If the key changes, the [`Pop`] widget will trigger again.
|
||||
/// If the key changes, the [`Sensor`] widget will trigger again.
|
||||
pub fn key<K>(
|
||||
self,
|
||||
key: K,
|
||||
) -> Pop<'a, impl self::Key, Message, Theme, Renderer>
|
||||
) -> Sensor<'a, impl self::Key, Message, Theme, Renderer>
|
||||
where
|
||||
K: Clone + PartialEq + 'static,
|
||||
{
|
||||
Pop {
|
||||
Sensor {
|
||||
content: self.content,
|
||||
key: OwnedKey(key),
|
||||
on_show: self.on_show,
|
||||
|
|
@ -103,18 +104,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the key of the [`Pop`] widget, for continuity; using a reference.
|
||||
/// Sets the key of the [`Sensor`], for continuity; using a reference.
|
||||
///
|
||||
/// If the key changes, the [`Pop`] widget will trigger again.
|
||||
/// If the key changes, the [`Sensor`] will trigger again.
|
||||
pub fn key_ref<K>(
|
||||
self,
|
||||
key: &'a K,
|
||||
) -> Pop<'a, &'a K, Message, Theme, Renderer>
|
||||
) -> Sensor<'a, &'a K, Message, Theme, Renderer>
|
||||
where
|
||||
K: ToOwned + PartialEq<K::Owned> + ?Sized,
|
||||
K::Owned: 'static,
|
||||
{
|
||||
Pop {
|
||||
Sensor {
|
||||
content: self.content,
|
||||
key,
|
||||
on_show: self.on_show,
|
||||
|
|
@ -158,7 +159,7 @@ struct State<Key> {
|
|||
}
|
||||
|
||||
impl<Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for Pop<'_, Key, Message, Theme, Renderer>
|
||||
for Sensor<'_, Key, Message, Theme, Renderer>
|
||||
where
|
||||
Key: self::Key,
|
||||
Renderer: core::Renderer,
|
||||
|
|
@ -212,7 +213,16 @@ where
|
|||
|
||||
let distance = top_left_distance.min(bottom_right_distance);
|
||||
|
||||
if state.has_popped_in {
|
||||
if self.on_show.is_none() {
|
||||
if let Some(on_resize) = &self.on_resize {
|
||||
let size = bounds.size();
|
||||
|
||||
if Some(size) != state.last_size {
|
||||
state.last_size = Some(size);
|
||||
shell.publish(on_resize(size));
|
||||
}
|
||||
}
|
||||
} else if state.has_popped_in {
|
||||
if distance <= self.anticipate.0 {
|
||||
if let Some(on_resize) = &self.on_resize {
|
||||
let size = bounds.size();
|
||||
|
|
@ -226,7 +236,7 @@ where
|
|||
state.has_popped_in = false;
|
||||
state.should_notify_at = Some((false, *now + self.delay));
|
||||
}
|
||||
} else if self.on_show.is_some() && distance <= self.anticipate.0 {
|
||||
} else if distance <= self.anticipate.0 {
|
||||
let size = bounds.size();
|
||||
|
||||
state.has_popped_in = true;
|
||||
|
|
@ -356,7 +366,7 @@ where
|
|||
}
|
||||
|
||||
impl<'a, Key, Message, Theme, Renderer>
|
||||
From<Pop<'a, Key, Message, Theme, Renderer>>
|
||||
From<Sensor<'a, Key, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
|
|
@ -364,7 +374,7 @@ where
|
|||
Renderer: core::Renderer + 'a,
|
||||
Theme: 'a,
|
||||
{
|
||||
fn from(pop: Pop<'a, Key, Message, Theme, Renderer>) -> Self {
|
||||
fn from(pop: Sensor<'a, Key, Message, Theme, Renderer>) -> Self {
|
||||
Element::new(pop)
|
||||
}
|
||||
}
|
||||
727
widget/src/table.rs
Normal file
727
widget/src/table.rs
Normal file
|
|
@ -0,0 +1,727 @@
|
|||
//! Display tables.
|
||||
use crate::core;
|
||||
use crate::core::alignment;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::widget;
|
||||
use crate::core::{
|
||||
Alignment, Background, Element, Layout, Length, Pixels, Rectangle, Size,
|
||||
Widget,
|
||||
};
|
||||
|
||||
/// Creates a new [`Table`] with the given columns and rows.
|
||||
///
|
||||
/// Columns can be created using the [`column()`] function, while rows can be any
|
||||
/// iterator over some data type `T`.
|
||||
pub fn table<'a, 'b, T, Message, Theme, Renderer>(
|
||||
columns: impl IntoIterator<Item = Column<'a, 'b, T, Message, Theme, Renderer>>,
|
||||
rows: impl IntoIterator<Item = T>,
|
||||
) -> Table<'a, Message, Theme, Renderer>
|
||||
where
|
||||
T: Clone,
|
||||
Theme: Catalog,
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
Table::new(columns, rows)
|
||||
}
|
||||
|
||||
/// Creates a new [`Column`] with the given header and view function.
|
||||
///
|
||||
/// The view function will be called for each row in a [`Table`] and it must
|
||||
/// produce the resulting contents of a cell.
|
||||
pub fn column<'a, 'b, T, E, Message, Theme, Renderer>(
|
||||
header: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
view: impl Fn(T) -> E + 'b,
|
||||
) -> Column<'a, 'b, T, Message, Theme, Renderer>
|
||||
where
|
||||
T: 'a,
|
||||
E: Into<Element<'a, Message, Theme, Renderer>>,
|
||||
{
|
||||
Column {
|
||||
header: header.into(),
|
||||
view: Box::new(move |data| view(data).into()),
|
||||
width: Length::Shrink,
|
||||
align_x: alignment::Horizontal::Left,
|
||||
align_y: alignment::Vertical::Top,
|
||||
}
|
||||
}
|
||||
|
||||
/// A grid-like visual representation of data distributed in columns and rows.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Table<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
|
||||
where
|
||||
Theme: Catalog,
|
||||
{
|
||||
columns: Vec<Column_>,
|
||||
cells: Vec<Element<'a, Message, Theme, Renderer>>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
padding_x: f32,
|
||||
padding_y: f32,
|
||||
separator_x: f32,
|
||||
separator_y: f32,
|
||||
class: Theme::Class<'a>,
|
||||
}
|
||||
|
||||
struct Column_ {
|
||||
width: Length,
|
||||
align_x: alignment::Horizontal,
|
||||
align_y: alignment::Vertical,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Table<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: Catalog,
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
/// Creates a new [`Table`] with the given columns and rows.
|
||||
///
|
||||
/// Columns can be created using the [`column()`] function, while rows can be any
|
||||
/// iterator over some data type `T`.
|
||||
pub fn new<'b, T>(
|
||||
columns: impl IntoIterator<
|
||||
Item = Column<'a, 'b, T, Message, Theme, Renderer>,
|
||||
>,
|
||||
rows: impl IntoIterator<Item = T>,
|
||||
) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
let columns = columns.into_iter();
|
||||
let rows = rows.into_iter();
|
||||
|
||||
let mut width = Length::Shrink;
|
||||
let mut height = Length::Shrink;
|
||||
|
||||
let mut cells = Vec::with_capacity(
|
||||
columns.size_hint().0 * (1 + rows.size_hint().0),
|
||||
);
|
||||
|
||||
let (mut columns, views): (Vec<_>, Vec<_>) = columns
|
||||
.map(|column| {
|
||||
width = width.enclose(column.width);
|
||||
|
||||
cells.push(column.header);
|
||||
|
||||
(
|
||||
Column_ {
|
||||
width: column.width,
|
||||
align_x: column.align_x,
|
||||
align_y: column.align_y,
|
||||
},
|
||||
column.view,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for row in rows {
|
||||
for view in &views {
|
||||
let cell = view(row.clone());
|
||||
let size_hint = cell.as_widget().size_hint();
|
||||
|
||||
height = height.enclose(size_hint.height);
|
||||
|
||||
cells.push(cell);
|
||||
}
|
||||
}
|
||||
|
||||
if width == Length::Shrink
|
||||
&& let Some(first) = columns.first_mut()
|
||||
{
|
||||
first.width = Length::Fill;
|
||||
}
|
||||
|
||||
Self {
|
||||
columns,
|
||||
cells,
|
||||
width,
|
||||
height,
|
||||
padding_x: 10.0,
|
||||
padding_y: 5.0,
|
||||
separator_x: 1.0,
|
||||
separator_y: 1.0,
|
||||
class: Theme::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Table`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the padding of the cells of the [`Table`].
|
||||
pub fn padding(self, padding: impl Into<Pixels>) -> Self {
|
||||
let padding = padding.into();
|
||||
|
||||
self.padding_x(padding).padding_y(padding)
|
||||
}
|
||||
|
||||
/// Sets the horizontal padding of the cells of the [`Table`].
|
||||
pub fn padding_x(mut self, padding: impl Into<Pixels>) -> Self {
|
||||
self.padding_x = padding.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the vertical padding of the cells of the [`Table`].
|
||||
pub fn padding_y(mut self, padding: impl Into<Pixels>) -> Self {
|
||||
self.padding_y = padding.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the thickness of the line separator between the cells of the [`Table`].
|
||||
pub fn separator(self, separator: impl Into<Pixels>) -> Self {
|
||||
let separator = separator.into();
|
||||
|
||||
self.separator_x(separator).separator_y(separator)
|
||||
}
|
||||
|
||||
/// Sets the thickness of the horizontal line separator between the cells of the [`Table`].
|
||||
pub fn separator_x(mut self, separator: impl Into<Pixels>) -> Self {
|
||||
self.separator_x = separator.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the thickness of the vertical line separator between the cells of the [`Table`].
|
||||
pub fn separator_y(mut self, separator: impl Into<Pixels>) -> Self {
|
||||
self.separator_y = separator.into().0;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct Metrics {
|
||||
columns: Vec<f32>,
|
||||
rows: Vec<f32>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for Table<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: Catalog,
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
fn tag(&self) -> widget::tree::Tag {
|
||||
widget::tree::Tag::of::<Metrics>()
|
||||
}
|
||||
|
||||
fn state(&self) -> widget::tree::State {
|
||||
widget::tree::State::new(Metrics {
|
||||
columns: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
self.cells
|
||||
.iter()
|
||||
.map(|cell| widget::Tree::new(cell.as_widget()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn diff(&self, state: &mut widget::Tree) {
|
||||
state.diff_children(&self.cells);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let metrics = tree.state.downcast_mut::<Metrics>();
|
||||
let columns = self.columns.len();
|
||||
let rows = self.cells.len() / columns;
|
||||
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
let available = limits.max();
|
||||
let table_fluid = self.width.fluid();
|
||||
|
||||
let mut cells = Vec::with_capacity(self.cells.len());
|
||||
cells.resize(self.cells.len(), layout::Node::default());
|
||||
|
||||
metrics.columns = vec![0.0; self.columns.len()];
|
||||
metrics.rows = vec![0.0; rows];
|
||||
|
||||
let mut column_factors = vec![0; self.columns.len()];
|
||||
let mut total_row_factors = 0;
|
||||
let mut total_fluid_height = 0.0;
|
||||
let mut row_factor = 0;
|
||||
|
||||
let spacing_x = self.padding_x * 2.0 + self.separator_x;
|
||||
let spacing_y = self.padding_y * 2.0 + self.separator_y;
|
||||
|
||||
// FIRST PASS
|
||||
// Lay out non-fluid cells
|
||||
let mut x = self.padding_x;
|
||||
let mut y = self.padding_y;
|
||||
|
||||
for (i, (cell, state)) in
|
||||
self.cells.iter().zip(&mut tree.children).enumerate()
|
||||
{
|
||||
let row = i / columns;
|
||||
let column = i % columns;
|
||||
|
||||
let width = self.columns[column].width;
|
||||
let size = cell.as_widget().size();
|
||||
|
||||
if column == 0 {
|
||||
x = self.padding_x;
|
||||
|
||||
if row > 0 {
|
||||
y += metrics.rows[row - 1] + spacing_y;
|
||||
|
||||
if row_factor != 0 {
|
||||
total_fluid_height += metrics.rows[row - 1];
|
||||
total_row_factors += row_factor;
|
||||
|
||||
row_factor = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let width_factor = width.fill_factor();
|
||||
let height_factor = size.height.fill_factor();
|
||||
|
||||
if width_factor != 0 || height_factor != 0 || size.width.is_fill() {
|
||||
column_factors[column] =
|
||||
column_factors[column].max(width_factor);
|
||||
|
||||
row_factor = row_factor.max(height_factor);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let limits = layout::Limits::new(
|
||||
Size::ZERO,
|
||||
Size::new(available.width - x, available.height - y),
|
||||
)
|
||||
.width(width);
|
||||
|
||||
let layout = cell.as_widget().layout(state, renderer, &limits);
|
||||
let size = limits.resolve(width, Length::Shrink, layout.size());
|
||||
|
||||
metrics.columns[column] = metrics.columns[column].max(size.width);
|
||||
metrics.rows[row] = metrics.rows[row].max(size.height);
|
||||
cells[i] = layout;
|
||||
|
||||
x += size.width + spacing_x;
|
||||
}
|
||||
|
||||
// SECOND PASS
|
||||
// Lay out fluid cells, using metrics from the first pass as limits
|
||||
let left = Size::new(
|
||||
available.width
|
||||
- metrics
|
||||
.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| column_factors[*i] == 0)
|
||||
.map(|(_, width)| width)
|
||||
.sum::<f32>(),
|
||||
available.height - total_fluid_height,
|
||||
);
|
||||
|
||||
let width_unit = (left.width
|
||||
- spacing_x * self.columns.len().saturating_sub(1) as f32
|
||||
- self.padding_x * 2.0)
|
||||
/ column_factors.iter().sum::<u16>() as f32;
|
||||
|
||||
let height_unit = (left.height
|
||||
- spacing_y * rows.saturating_sub(1) as f32
|
||||
- self.padding_y * 2.0)
|
||||
/ total_row_factors as f32;
|
||||
|
||||
let mut x = self.padding_x;
|
||||
let mut y = self.padding_y;
|
||||
|
||||
for (i, (cell, state)) in
|
||||
self.cells.iter().zip(&mut tree.children).enumerate()
|
||||
{
|
||||
let row = i / columns;
|
||||
let column = i % columns;
|
||||
|
||||
let size = cell.as_widget().size();
|
||||
|
||||
let width = self.columns[column].width;
|
||||
let width_factor = width.fill_factor();
|
||||
let height_factor = size.height.fill_factor();
|
||||
|
||||
if column == 0 {
|
||||
x = self.padding_x;
|
||||
|
||||
if row > 0 {
|
||||
y += metrics.rows[row - 1] + spacing_y;
|
||||
}
|
||||
}
|
||||
|
||||
if width_factor == 0
|
||||
&& size.width.fill_factor() == 0
|
||||
&& size.height.fill_factor() == 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let max_width = if width_factor == 0 {
|
||||
if size.width.is_fill() {
|
||||
metrics.columns[column]
|
||||
} else {
|
||||
(available.width - x).max(0.0)
|
||||
}
|
||||
} else {
|
||||
width_unit * width_factor as f32
|
||||
};
|
||||
|
||||
let max_height = if height_factor == 0 {
|
||||
if size.height.is_fill() {
|
||||
metrics.rows[row]
|
||||
} else {
|
||||
(available.height - y).max(0.0)
|
||||
}
|
||||
} else {
|
||||
height_unit * height_factor as f32
|
||||
};
|
||||
|
||||
let limits = layout::Limits::new(
|
||||
Size::ZERO,
|
||||
Size::new(max_width, max_height),
|
||||
)
|
||||
.width(width);
|
||||
|
||||
let layout = cell.as_widget().layout(state, renderer, &limits);
|
||||
let size = limits.resolve(
|
||||
if let Length::Fixed(_) = width {
|
||||
width
|
||||
} else {
|
||||
table_fluid
|
||||
},
|
||||
Length::Shrink,
|
||||
layout.size(),
|
||||
);
|
||||
|
||||
metrics.columns[column] = metrics.columns[column].max(size.width);
|
||||
metrics.rows[row] = metrics.rows[row].max(size.height);
|
||||
cells[i] = layout;
|
||||
|
||||
x += size.width + spacing_x;
|
||||
}
|
||||
|
||||
// THIRD PASS
|
||||
// Position each cell
|
||||
let mut x = self.padding_x;
|
||||
let mut y = self.padding_y;
|
||||
|
||||
for (i, cell) in cells.iter_mut().enumerate() {
|
||||
let row = i / columns;
|
||||
let column = i % columns;
|
||||
|
||||
if column == 0 {
|
||||
x = self.padding_x;
|
||||
|
||||
if row > 0 {
|
||||
y += metrics.rows[row - 1] + spacing_y;
|
||||
}
|
||||
}
|
||||
|
||||
let Column_ {
|
||||
align_x, align_y, ..
|
||||
} = &self.columns[column];
|
||||
|
||||
cell.move_to_mut((x, y));
|
||||
cell.align_mut(
|
||||
Alignment::from(*align_x),
|
||||
Alignment::from(*align_y),
|
||||
Size::new(metrics.columns[column], metrics.rows[row]),
|
||||
);
|
||||
|
||||
x += metrics.columns[column] + spacing_x;
|
||||
}
|
||||
|
||||
let intrinsic = limits.resolve(
|
||||
self.width,
|
||||
self.height,
|
||||
Size::new(
|
||||
x - spacing_x + self.padding_x,
|
||||
y + metrics
|
||||
.rows
|
||||
.last()
|
||||
.copied()
|
||||
.map(|height| height + self.padding_y)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
);
|
||||
|
||||
layout::Node::with_children(intrinsic, cells)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut widget::Tree,
|
||||
event: &core::Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn core::Clipboard,
|
||||
shell: &mut core::Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for ((cell, state), layout) in self
|
||||
.cells
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
cell.as_widget_mut().update(
|
||||
state, event, layout, cursor, renderer, clipboard, shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for ((cell, state), layout) in
|
||||
self.cells.iter().zip(&tree.children).zip(layout.children())
|
||||
{
|
||||
cell.as_widget()
|
||||
.draw(state, renderer, theme, style, layout, cursor, viewport);
|
||||
}
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let metrics = tree.state.downcast_ref::<Metrics>();
|
||||
let style = theme.style(&self.class);
|
||||
|
||||
if self.separator_x > 0.0 {
|
||||
let mut x = self.padding_x;
|
||||
|
||||
for width in
|
||||
&metrics.columns[..metrics.columns.len().saturating_sub(1)]
|
||||
{
|
||||
x += width + self.padding_x;
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + x,
|
||||
y: bounds.y,
|
||||
width: self.separator_x,
|
||||
height: bounds.height,
|
||||
},
|
||||
snap: true,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.separator_x,
|
||||
);
|
||||
|
||||
x += self.separator_x + self.padding_x;
|
||||
}
|
||||
}
|
||||
|
||||
if self.separator_y > 0.0 {
|
||||
let mut y = self.padding_y;
|
||||
|
||||
for height in &metrics.rows[..metrics.rows.len().saturating_sub(1)]
|
||||
{
|
||||
y += height + self.padding_y;
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y + y,
|
||||
width: bounds.width,
|
||||
height: self.separator_y,
|
||||
},
|
||||
snap: true,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.separator_y,
|
||||
);
|
||||
|
||||
y += self.separator_y + self.padding_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.cells
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((cell, state), layout)| {
|
||||
cell.as_widget().mouse_interaction(
|
||||
state, layout, cursor, viewport, renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation,
|
||||
) {
|
||||
for ((cell, state), layout) in self
|
||||
.cells
|
||||
.iter()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
cell.as_widget().operate(state, layout, renderer, operation);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut widget::Tree,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: core::Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
overlay::from_children(
|
||||
&mut self.cells,
|
||||
state,
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> From<Table<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: core::Renderer + 'a,
|
||||
{
|
||||
fn from(table: Table<'a, Message, Theme, Renderer>) -> Self {
|
||||
Element::new(table)
|
||||
}
|
||||
}
|
||||
|
||||
/// A vertical visualization of some data with a header.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Column<
|
||||
'a,
|
||||
'b,
|
||||
T,
|
||||
Message,
|
||||
Theme = crate::Theme,
|
||||
Renderer = crate::Renderer,
|
||||
> {
|
||||
header: Element<'a, Message, Theme, Renderer>,
|
||||
view: Box<dyn Fn(T) -> Element<'a, Message, Theme, Renderer> + 'b>,
|
||||
width: Length,
|
||||
align_x: alignment::Horizontal,
|
||||
align_y: alignment::Vertical,
|
||||
}
|
||||
|
||||
impl<'a, 'b, T, Message, Theme, Renderer>
|
||||
Column<'a, 'b, T, Message, Theme, Renderer>
|
||||
{
|
||||
/// Sets the width of the [`Column`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the alignment for the horizontal axis of the [`Column`].
|
||||
pub fn align_x(
|
||||
mut self,
|
||||
alignment: impl Into<alignment::Horizontal>,
|
||||
) -> Self {
|
||||
self.align_x = alignment.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the alignment for the vertical axis of the [`Column`].
|
||||
pub fn align_y(
|
||||
mut self,
|
||||
alignment: impl Into<alignment::Vertical>,
|
||||
) -> Self {
|
||||
self.align_y = alignment.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The appearance of a [`Table`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Style {
|
||||
/// The background color of the horizontal line separator between cells.
|
||||
pub separator_x: Background,
|
||||
/// The background color of the vertical line separator between cells.
|
||||
pub separator_y: Background,
|
||||
}
|
||||
|
||||
/// The theme catalog of a [`Table`].
|
||||
pub trait Catalog {
|
||||
/// The item class of the [`Catalog`].
|
||||
type Class<'a>;
|
||||
|
||||
/// The default class produced by the [`Catalog`].
|
||||
fn default<'a>() -> Self::Class<'a>;
|
||||
|
||||
/// The [`Style`] of a class with the given status.
|
||||
fn style(&self, class: &Self::Class<'_>) -> Style;
|
||||
}
|
||||
|
||||
/// A styling function for a [`Table`].
|
||||
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
|
||||
|
||||
impl<Theme> From<Style> for StyleFn<'_, Theme> {
|
||||
fn from(style: Style) -> Self {
|
||||
Box::new(move |_theme| style)
|
||||
}
|
||||
}
|
||||
|
||||
impl Catalog for crate::Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(default)
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The default style of a [`Table`].
|
||||
pub fn default(theme: &crate::Theme) -> Style {
|
||||
let palette = theme.extended_palette();
|
||||
let separator = palette.background.strong.color.into();
|
||||
|
||||
Style {
|
||||
separator_x: separator,
|
||||
separator_y: separator,
|
||||
}
|
||||
}
|
||||
|
|
@ -689,22 +689,20 @@ where
|
|||
}
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
if let Some(focus) = &mut state.focus {
|
||||
if focus.is_window_focused {
|
||||
focus.now = *now;
|
||||
if let Some(focus) = &mut state.focus
|
||||
&& focus.is_window_focused
|
||||
{
|
||||
focus.now = *now;
|
||||
|
||||
let millis_until_redraw =
|
||||
Focus::CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (focus.now - focus.updated_at).as_millis()
|
||||
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
let millis_until_redraw =
|
||||
Focus::CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (focus.now - focus.updated_at).as_millis()
|
||||
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
|
||||
shell.request_redraw_at(
|
||||
focus.now
|
||||
+ Duration::from_millis(
|
||||
millis_until_redraw as u64,
|
||||
),
|
||||
);
|
||||
}
|
||||
shell.request_redraw_at(
|
||||
focus.now
|
||||
+ Duration::from_millis(millis_until_redraw as u64),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -1374,8 +1372,6 @@ pub struct Style {
|
|||
pub background: Background,
|
||||
/// The [`Border`] of the text input.
|
||||
pub border: Border,
|
||||
/// The [`Color`] of the icon of the text input.
|
||||
pub icon: Color,
|
||||
/// The [`Color`] of the placeholder of the text input.
|
||||
pub placeholder: Color,
|
||||
/// The [`Color`] of the value of the text input.
|
||||
|
|
@ -1422,8 +1418,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
width: 1.0,
|
||||
color: palette.background.strong.color,
|
||||
},
|
||||
icon: palette.background.weak.text,
|
||||
placeholder: palette.background.strong.color,
|
||||
placeholder: palette.secondary.base.color,
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.weak.color,
|
||||
};
|
||||
|
|
@ -1447,6 +1442,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
Status::Disabled => Style {
|
||||
background: Background::Color(palette.background.weak.color),
|
||||
value: active.placeholder,
|
||||
placeholder: palette.background.strongest.color,
|
||||
..active
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1247,12 +1247,12 @@ where
|
|||
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
||||
let state = state::<Renderer>(tree);
|
||||
|
||||
if state.is_focused.is_some() {
|
||||
if let keyboard::Key::Character("v") = key.as_ref() {
|
||||
state.is_pasting = None;
|
||||
if state.is_focused.is_some()
|
||||
&& let keyboard::Key::Character("v") = key.as_ref()
|
||||
{
|
||||
state.is_pasting = None;
|
||||
|
||||
shell.capture_event();
|
||||
}
|
||||
shell.capture_event();
|
||||
}
|
||||
|
||||
state.is_pasting = None;
|
||||
|
|
@ -1328,32 +1328,31 @@ where
|
|||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
let state = state::<Renderer>(tree);
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
if focus.is_window_focused {
|
||||
if matches!(
|
||||
state.cursor.state(&self.value),
|
||||
cursor::State::Index(_)
|
||||
) {
|
||||
focus.now = *now;
|
||||
if let Some(focus) = &mut state.is_focused
|
||||
&& focus.is_window_focused
|
||||
{
|
||||
if matches!(
|
||||
state.cursor.state(&self.value),
|
||||
cursor::State::Index(_)
|
||||
) {
|
||||
focus.now = *now;
|
||||
|
||||
let millis_until_redraw =
|
||||
CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (*now - focus.updated_at).as_millis()
|
||||
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (*now - focus.updated_at).as_millis()
|
||||
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
|
||||
shell.request_redraw_at(
|
||||
*now + Duration::from_millis(
|
||||
millis_until_redraw as u64,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
shell.request_input_method(&self.input_method(
|
||||
state,
|
||||
layout,
|
||||
&self.value,
|
||||
));
|
||||
shell.request_redraw_at(
|
||||
*now + Duration::from_millis(
|
||||
millis_until_redraw as u64,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
shell.request_input_method(&self.input_method(
|
||||
state,
|
||||
layout,
|
||||
&self.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -1817,10 +1816,10 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
border: Border {
|
||||
radius: 2.0.into(),
|
||||
width: 1.0,
|
||||
color: palette.background.strongest.color,
|
||||
color: palette.background.strong.color,
|
||||
},
|
||||
icon: palette.background.weak.text,
|
||||
placeholder: palette.background.strongest.color,
|
||||
placeholder: palette.secondary.base.color,
|
||||
value: palette.background.base.text,
|
||||
selection: palette.primary.weak.color,
|
||||
};
|
||||
|
|
@ -1844,6 +1843,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
Status::Disabled => Style {
|
||||
background: Background::Color(palette.background.weak.color),
|
||||
value: active.placeholder,
|
||||
placeholder: palette.background.strongest.color,
|
||||
..active
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -394,9 +394,6 @@ where
|
|||
_cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
/// Makes sure that the border radius of the toggler looks good at every size.
|
||||
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
|
||||
|
||||
/// The space ratio between the background Quad and the Toggler bounds, and
|
||||
/// between the background Quad and foreground Quad.
|
||||
const SPACE_RATIO: f32 = 0.05;
|
||||
|
|
@ -423,7 +420,7 @@ where
|
|||
let style = theme
|
||||
.style(&self.class, self.last_status.unwrap_or(Status::Disabled));
|
||||
|
||||
let border_radius = bounds.height / BORDER_RADIUS_RATIO;
|
||||
let border_radius = bounds.height / 2.0;
|
||||
let space = (SPACE_RATIO * bounds.height).round();
|
||||
|
||||
let toggler_background_bounds = Rectangle {
|
||||
|
|
@ -557,7 +554,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
let background = match status {
|
||||
Status::Active { is_toggled } | Status::Hovered { is_toggled } => {
|
||||
if is_toggled {
|
||||
palette.primary.strong.color
|
||||
palette.primary.base.color
|
||||
} else {
|
||||
palette.background.strong.color
|
||||
}
|
||||
|
|
@ -568,7 +565,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
let foreground = match status {
|
||||
Status::Active { is_toggled } => {
|
||||
if is_toggled {
|
||||
palette.primary.strong.text
|
||||
palette.primary.base.text
|
||||
} else {
|
||||
palette.background.base.color
|
||||
}
|
||||
|
|
@ -577,13 +574,13 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
|||
if is_toggled {
|
||||
Color {
|
||||
a: 0.5,
|
||||
..palette.primary.strong.text
|
||||
..palette.primary.base.text
|
||||
}
|
||||
} else {
|
||||
palette.background.weak.color
|
||||
}
|
||||
}
|
||||
Status::Disabled => palette.background.base.color,
|
||||
Status::Disabled => palette.background.weakest.color,
|
||||
};
|
||||
|
||||
Style {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue