Merge branch 'master' into feature/test-recorder
This commit is contained in:
commit
26c9dc1709
83 changed files with 2627 additions and 1208 deletions
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
rust: [stable, beta, "1.85"]
|
rust: [stable, beta, "1.88"]
|
||||||
steps:
|
steps:
|
||||||
- uses: hecrj/setup-rust-action@v2
|
- uses: hecrj/setup-rust-action@v2
|
||||||
with:
|
with:
|
||||||
|
|
@ -23,5 +23,7 @@ jobs:
|
||||||
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
|
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
|
cargo test --verbose --workspace
|
||||||
cargo test --verbose --workspace -- --ignored
|
cargo test --verbose --workspace -- --ignored
|
||||||
|
cargo test --verbose --workspace --all-features
|
||||||
cargo test --verbose --workspace --all-features -- --ignored
|
cargo test --verbose --workspace --all-features -- --ignored
|
||||||
|
|
|
||||||
883
Cargo.lock
generated
883
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -22,7 +22,7 @@ all-features = true
|
||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu", "tiny-skia", "web-colors", "auto-detect-theme", "thread-pool"]
|
default = ["wgpu", "tiny-skia", "crisp", "web-colors", "auto-detect-theme", "thread-pool"]
|
||||||
# Enables the `wgpu` GPU-accelerated renderer backend
|
# Enables the `wgpu` GPU-accelerated renderer backend
|
||||||
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||||
# Enables the `tiny-skia` software renderer backend
|
# Enables the `tiny-skia` software renderer backend
|
||||||
|
|
@ -149,7 +149,7 @@ repository = "https://github.com/iced-rs/iced"
|
||||||
homepage = "https://iced.rs"
|
homepage = "https://iced.rs"
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||||
rust-version = "1.85"
|
rust-version = "1.88"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
iced = { version = "0.14.0-dev", path = "." }
|
iced = { version = "0.14.0-dev", path = "." }
|
||||||
|
|
@ -178,7 +178,7 @@ cosmic-text = "0.14"
|
||||||
dark-light = "2.0"
|
dark-light = "2.0"
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
glam = "0.25"
|
glam = "0.25"
|
||||||
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "a456d1c17bbcf33afcca41d9e5e299f9f1193819" }
|
cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "453cedec0d2ec563bd7fa87e84a2319bcebb1ba3" }
|
||||||
guillotiere = "0.6"
|
guillotiere = "0.6"
|
||||||
half = "2.2"
|
half = "2.2"
|
||||||
image = { version = "0.25", default-features = false }
|
image = { version = "0.25", default-features = false }
|
||||||
|
|
@ -217,7 +217,7 @@ wasm-bindgen-futures = "0.4"
|
||||||
wasmtimer = "0.4.1"
|
wasmtimer = "0.4.1"
|
||||||
web-sys = "0.3.69"
|
web-sys = "0.3.69"
|
||||||
web-time = "1.1"
|
web-time = "1.1"
|
||||||
wgpu = "24.0"
|
wgpu = "26.0"
|
||||||
window_clipboard = "0.4.1"
|
window_clipboard = "0.4.1"
|
||||||
winit = { git = "https://github.com/iced-rs/winit.git", rev = "11414b6aa45699f038114e61b4ddf5102b2d3b4b" }
|
winit = { git = "https://github.com/iced-rs/winit.git", rev = "11414b6aa45699f038114e61b4ddf5102b2d3b4b" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,15 @@ pub fn wgpu_benchmark(c: &mut Criterion) {
|
||||||
))
|
))
|
||||||
.expect("request adapter");
|
.expect("request adapter");
|
||||||
|
|
||||||
let (device, queue) = executor::block_on(adapter.request_device(
|
let (device, queue) =
|
||||||
&wgpu::DeviceDescriptor {
|
executor::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
required_features: wgpu::Features::empty(),
|
required_features: wgpu::Features::empty(),
|
||||||
required_limits: wgpu::Limits::default(),
|
required_limits: wgpu::Limits::default(),
|
||||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||||
},
|
trace: wgpu::Trace::Off,
|
||||||
None,
|
}))
|
||||||
))
|
.expect("request device");
|
||||||
.expect("request device");
|
|
||||||
|
|
||||||
c.bench_function("wgpu — canvas (light)", |b| {
|
c.bench_function("wgpu — canvas (light)", |b| {
|
||||||
benchmark(b, &adapter, &device, &queue, |_| scene(10));
|
benchmark(b, &adapter, &device, &queue, |_| scene(10));
|
||||||
|
|
@ -140,7 +139,7 @@ fn benchmark<'a>(
|
||||||
&viewport,
|
&viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
let _ = device.poll(wgpu::PollType::WaitForSubmissionIndex(submission));
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -90,12 +90,12 @@ impl Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self::new(
|
||||||
r: gamma_component(r),
|
gamma_component(r),
|
||||||
g: gamma_component(g),
|
gamma_component(g),
|
||||||
b: gamma_component(b),
|
gamma_component(b),
|
||||||
a,
|
a,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a [`Color`] from a hex string.
|
/// Parses a [`Color`] from a hex string.
|
||||||
|
|
@ -195,6 +195,13 @@ impl Color {
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the relative luminance of the [`Color`].
|
||||||
|
/// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
||||||
|
pub fn relative_luminance(self) -> f32 {
|
||||||
|
let linear = self.into_linear();
|
||||||
|
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[f32; 3]> for Color {
|
impl From<[f32; 3]> for Color {
|
||||||
|
|
|
||||||
|
|
@ -532,3 +532,49 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T, Message, Theme, Renderer> From<Option<T>>
|
||||||
|
for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
T: Into<Self>,
|
||||||
|
Renderer: crate::Renderer,
|
||||||
|
{
|
||||||
|
fn from(element: Option<T>) -> Self {
|
||||||
|
struct Void;
|
||||||
|
|
||||||
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Void
|
||||||
|
where
|
||||||
|
Renderer: crate::Renderer,
|
||||||
|
{
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
Size {
|
||||||
|
width: Length::Fixed(0.0),
|
||||||
|
height: Length::Fixed(0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
_tree: &mut Tree,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
_limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
layout::Node::new(Size::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_tree: &Tree,
|
||||||
|
_renderer: &mut Renderer,
|
||||||
|
_theme: &Theme,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
_layout: Layout<'_>,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.map(T::into).unwrap_or_else(|| Element::new(Void))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,22 +52,22 @@ impl Node {
|
||||||
/// Aligns the [`Node`] in the given space.
|
/// Aligns the [`Node`] in the given space.
|
||||||
pub fn align(
|
pub fn align(
|
||||||
mut self,
|
mut self,
|
||||||
horizontal_alignment: Alignment,
|
align_x: Alignment,
|
||||||
vertical_alignment: Alignment,
|
align_y: Alignment,
|
||||||
space: Size,
|
space: Size,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.align_mut(horizontal_alignment, vertical_alignment, space);
|
self.align_mut(align_x, align_y, space);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutable reference version of [`Self::align`].
|
/// Mutable reference version of [`Self::align`].
|
||||||
pub fn align_mut(
|
pub fn align_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
horizontal_alignment: Alignment,
|
align_x: Alignment,
|
||||||
vertical_alignment: Alignment,
|
align_y: Alignment,
|
||||||
space: Size,
|
space: Size,
|
||||||
) {
|
) {
|
||||||
match horizontal_alignment {
|
match align_x {
|
||||||
Alignment::Start => {}
|
Alignment::Start => {}
|
||||||
Alignment::Center => {
|
Alignment::Center => {
|
||||||
self.bounds.x += (space.width - self.bounds.width) / 2.0;
|
self.bounds.x += (space.width - self.bounds.width) / 2.0;
|
||||||
|
|
@ -77,7 +77,7 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match vertical_alignment {
|
match align_y {
|
||||||
Alignment::Start => {}
|
Alignment::Start => {}
|
||||||
Alignment::Center => {
|
Alignment::Center => {
|
||||||
self.bounds.y += (space.height - self.bounds.height) / 2.0;
|
self.bounds.y += (space.height - self.bounds.height) / 2.0;
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ impl Length {
|
||||||
|
|
||||||
/// Adapts the [`Length`] so it can contain the other [`Length`] and
|
/// Adapts the [`Length`] so it can contain the other [`Length`] and
|
||||||
/// match its fluidity.
|
/// match its fluidity.
|
||||||
|
#[inline]
|
||||||
pub fn enclose(self, other: Length) -> Self {
|
pub fn enclose(self, other: Length) -> Self {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Length::Shrink, Length::Fill | Length::FillPortion(_)) => other,
|
(Length::Shrink, Length::Fill | Length::FillPortion(_)) => other,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Radians, Vector};
|
use crate::{Length, Radians, Vector};
|
||||||
|
|
||||||
/// An amount of space in 2 dimensions.
|
/// An amount of space in 2 dimensions.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||||
|
|
@ -66,6 +66,15 @@ impl Size {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Size<Length> {
|
||||||
|
/// Returns true if either `width` or `height` are 0-sized.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_void(&self) -> bool {
|
||||||
|
matches!(self.width, Length::Fixed(0.0))
|
||||||
|
|| matches!(self.height, Length::Fixed(0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> From<[T; 2]> for Size<T> {
|
impl<T> From<[T; 2]> for Size<T> {
|
||||||
fn from([width, height]: [T; 2]) -> Self {
|
fn from([width, height]: [T; 2]) -> Self {
|
||||||
Size { width, height }
|
Size { width, height }
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ impl Palette {
|
||||||
text: Color::BLACK,
|
text: Color::BLACK,
|
||||||
primary: color!(0x5865F2),
|
primary: color!(0x5865F2),
|
||||||
success: color!(0x12664f),
|
success: color!(0x12664f),
|
||||||
warning: color!(0xffc14e),
|
warning: color!(0xb77e33),
|
||||||
danger: color!(0xc3423f),
|
danger: color!(0xc3423f),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -453,9 +453,15 @@ pub struct Background {
|
||||||
/// The weakest version of the base background color.
|
/// The weakest version of the base background color.
|
||||||
pub weakest: Pair,
|
pub weakest: Pair,
|
||||||
/// A weaker version of the base background color.
|
/// A weaker version of the base background color.
|
||||||
|
pub weaker: Pair,
|
||||||
|
/// A weak version of the base background color.
|
||||||
pub weak: Pair,
|
pub weak: Pair,
|
||||||
/// A stronger version of the base background color.
|
/// A neutral version of the base background color, between weak and strong.
|
||||||
|
pub neutral: Pair,
|
||||||
|
/// A strong version of the base background color.
|
||||||
pub strong: Pair,
|
pub strong: Pair,
|
||||||
|
/// A stronger version of the base background color.
|
||||||
|
pub stronger: Pair,
|
||||||
/// The strongest version of the base background color.
|
/// The strongest version of the base background color.
|
||||||
pub strongest: Pair,
|
pub strongest: Pair,
|
||||||
}
|
}
|
||||||
|
|
@ -464,15 +470,21 @@ impl Background {
|
||||||
/// Generates a set of [`Background`] colors from the base and text colors.
|
/// Generates a set of [`Background`] colors from the base and text colors.
|
||||||
pub fn new(base: Color, text: Color) -> Self {
|
pub fn new(base: Color, text: Color) -> Self {
|
||||||
let weakest = deviate(base, 0.03);
|
let weakest = deviate(base, 0.03);
|
||||||
let weak = muted(deviate(base, 0.1));
|
let weaker = deviate(base, 0.07);
|
||||||
let strong = muted(deviate(base, 0.2));
|
let weak = deviate(base, 0.1);
|
||||||
let strongest = muted(deviate(base, 0.3));
|
let neutral = deviate(base, 0.125);
|
||||||
|
let strong = deviate(base, 0.15);
|
||||||
|
let stronger = deviate(base, 0.175);
|
||||||
|
let strongest = deviate(base, 0.20);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base: Pair::new(base, text),
|
base: Pair::new(base, text),
|
||||||
weakest: Pair::new(weakest, text),
|
weakest: Pair::new(weakest, text),
|
||||||
|
weaker: Pair::new(weaker, text),
|
||||||
weak: Pair::new(weak, text),
|
weak: Pair::new(weak, text),
|
||||||
|
neutral: Pair::new(neutral, text),
|
||||||
strong: Pair::new(strong, text),
|
strong: Pair::new(strong, text),
|
||||||
|
stronger: Pair::new(stronger, text),
|
||||||
strongest: Pair::new(strongest, text),
|
strongest: Pair::new(strongest, text),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -517,9 +529,11 @@ pub struct Secondary {
|
||||||
impl Secondary {
|
impl Secondary {
|
||||||
/// Generates a set of [`Secondary`] colors from the base and text colors.
|
/// Generates a set of [`Secondary`] colors from the base and text colors.
|
||||||
pub fn generate(base: Color, text: Color) -> Self {
|
pub fn generate(base: Color, text: Color) -> Self {
|
||||||
let base = mix(base, text, 0.2);
|
let factor = if is_dark(base) { 0.2 } else { 0.4 };
|
||||||
let weak = mix(base, text, 0.1);
|
|
||||||
let strong = mix(base, text, 0.3);
|
let weak = mix(deviate(base, 0.1), text, factor);
|
||||||
|
let strong = mix(deviate(base, 0.3), text, factor);
|
||||||
|
let base = mix(deviate(base, 0.2), text, factor);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base: Pair::new(base, text),
|
base: Pair::new(base, text),
|
||||||
|
|
@ -604,53 +618,55 @@ impl Danger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Hsl {
|
struct Oklch {
|
||||||
h: f32,
|
|
||||||
s: f32,
|
|
||||||
l: f32,
|
l: f32,
|
||||||
|
c: f32,
|
||||||
|
h: f32,
|
||||||
a: f32,
|
a: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn darken(color: Color, amount: f32) -> Color {
|
fn darken(color: Color, amount: f32) -> Color {
|
||||||
let mut hsl = to_hsl(color);
|
let mut oklch = to_oklch(color);
|
||||||
|
|
||||||
hsl.l = if hsl.l - amount < 0.0 {
|
// We try to bump the chroma a bit for more colorful palettes
|
||||||
|
if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 {
|
||||||
|
// Formula empirically and cluelessly derived
|
||||||
|
oklch.c *= 1.0 + (0.2 / oklch.c).min(100.0) * amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
oklch.l = if oklch.l - amount < 0.0 {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
hsl.l - amount
|
oklch.l - amount
|
||||||
};
|
};
|
||||||
|
|
||||||
from_hsl(hsl)
|
from_oklch(oklch)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lighten(color: Color, amount: f32) -> Color {
|
fn lighten(color: Color, amount: f32) -> Color {
|
||||||
let mut hsl = to_hsl(color);
|
let mut oklch = to_oklch(color);
|
||||||
|
|
||||||
hsl.l = if hsl.l + amount > 1.0 {
|
// We try to bump the chroma a bit for more colorful palettes
|
||||||
|
// Formula empirically and cluelessly derived
|
||||||
|
oklch.c *= 1.0 + 2.0 * amount / oklch.l.max(0.05);
|
||||||
|
|
||||||
|
oklch.l = if oklch.l + amount > 1.0 {
|
||||||
1.0
|
1.0
|
||||||
} else {
|
} else {
|
||||||
hsl.l + amount
|
oklch.l + amount
|
||||||
};
|
};
|
||||||
|
|
||||||
from_hsl(hsl)
|
from_oklch(oklch)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deviate(color: Color, amount: f32) -> Color {
|
fn deviate(color: Color, amount: f32) -> Color {
|
||||||
if is_dark(color) {
|
if is_dark(color) {
|
||||||
lighten(color, amount)
|
lighten(color, amount)
|
||||||
} else {
|
} else {
|
||||||
darken(color, amount * 0.8)
|
darken(color, amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn muted(color: Color) -> Color {
|
|
||||||
let mut hsl = to_hsl(color);
|
|
||||||
|
|
||||||
hsl.s = hsl.s.min(0.5);
|
|
||||||
|
|
||||||
from_hsl(hsl)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mix(a: Color, b: Color, factor: f32) -> Color {
|
fn mix(a: Color, b: Color, factor: f32) -> Color {
|
||||||
let b_amount = factor.clamp(0.0, 1.0);
|
let b_amount = factor.clamp(0.0, 1.0);
|
||||||
let a_amount = 1.0 - b_amount;
|
let a_amount = 1.0 - b_amount;
|
||||||
|
|
@ -680,6 +696,12 @@ fn readable(background: Color, text: Color) -> Color {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let candidate = improve(text, 0.2);
|
||||||
|
|
||||||
|
if is_readable(background, candidate) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
let white_contrast = relative_contrast(background, Color::WHITE);
|
let white_contrast = relative_contrast(background, Color::WHITE);
|
||||||
let black_contrast = relative_contrast(background, Color::BLACK);
|
let black_contrast = relative_contrast(background, Color::BLACK);
|
||||||
|
|
||||||
|
|
@ -691,85 +713,71 @@ fn readable(background: Color, text: Color) -> Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dark(color: Color) -> bool {
|
fn is_dark(color: Color) -> bool {
|
||||||
to_hsl(color).l < 0.6
|
to_oklch(color).l < 0.6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_readable(a: Color, b: Color) -> bool {
|
fn is_readable(a: Color, b: Color) -> bool {
|
||||||
relative_contrast(a, b) >= 7.0
|
relative_contrast(a, b) >= 6.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
|
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
|
||||||
fn relative_contrast(a: Color, b: Color) -> f32 {
|
fn relative_contrast(a: Color, b: Color) -> f32 {
|
||||||
let lum_a = relative_luminance(a);
|
let lum_a = a.relative_luminance();
|
||||||
let lum_b = relative_luminance(b);
|
let lum_b = b.relative_luminance();
|
||||||
(lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
|
(lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
|
||||||
fn relative_luminance(color: Color) -> f32 {
|
fn to_oklch(color: Color) -> Oklch {
|
||||||
let linear = color.into_linear();
|
let [r, g, b, alpha] = color.into_linear();
|
||||||
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
|
|
||||||
|
// linear RGB → LMS
|
||||||
|
let l = 0.41222146 * r + 0.53633255 * g + 0.051445995 * b;
|
||||||
|
let m = 0.2119035 * r + 0.6806995 * g + 0.10739696 * b;
|
||||||
|
let s = 0.08830246 * r + 0.28171885 * g + 0.6299787 * b;
|
||||||
|
|
||||||
|
// Nonlinear transform (cube root)
|
||||||
|
let l_ = l.cbrt();
|
||||||
|
let m_ = m.cbrt();
|
||||||
|
let s_ = s.cbrt();
|
||||||
|
|
||||||
|
// LMS → Oklab
|
||||||
|
let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
|
||||||
|
let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
|
||||||
|
let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
|
||||||
|
|
||||||
|
// Oklab → Oklch
|
||||||
|
let c = (a * a + b * b).sqrt();
|
||||||
|
let h = b.atan2(a); // radians
|
||||||
|
|
||||||
|
Oklch { l, c, h, a: alpha }
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
|
// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
|
||||||
fn to_hsl(color: Color) -> Hsl {
|
fn from_oklch(oklch: Oklch) -> Color {
|
||||||
let x_max = color.r.max(color.g).max(color.b);
|
let Oklch { l, c, h, a: alpha } = oklch;
|
||||||
let x_min = color.r.min(color.g).min(color.b);
|
|
||||||
let c = x_max - x_min;
|
|
||||||
let l = x_max.midpoint(x_min);
|
|
||||||
|
|
||||||
let h = if c == 0.0 {
|
let a = c * h.cos();
|
||||||
0.0
|
let b = c * h.sin();
|
||||||
} else if x_max == color.r {
|
|
||||||
60.0 * ((color.g - color.b) / c).rem_euclid(6.0)
|
|
||||||
} else if x_max == color.g {
|
|
||||||
60.0 * (((color.b - color.r) / c) + 2.0)
|
|
||||||
} else {
|
|
||||||
// x_max == color.b
|
|
||||||
60.0 * (((color.r - color.g) / c) + 4.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
let s = if l == 0.0 || l == 1.0 {
|
// Oklab → LMS (nonlinear)
|
||||||
0.0
|
let l_ = l + 0.39633778 * a + 0.21580376 * b;
|
||||||
} else {
|
let m_ = l - 0.105561346 * a - 0.06385417 * b;
|
||||||
(x_max - l) / l.min(1.0 - l)
|
let s_ = l - 0.08948418 * a - 1.2914855 * b;
|
||||||
};
|
|
||||||
|
|
||||||
Hsl {
|
// Cubing back
|
||||||
h,
|
let l = l_ * l_ * l_;
|
||||||
s,
|
let m = m_ * m_ * m_;
|
||||||
l,
|
let s = s_ * s_ * s_;
|
||||||
a: color.a,
|
|
||||||
}
|
let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
|
||||||
}
|
let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
|
||||||
|
let b = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
|
||||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
|
|
||||||
fn from_hsl(hsl: Hsl) -> Color {
|
Color::from_linear_rgba(
|
||||||
let c = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s;
|
r.clamp(0.0, 1.0),
|
||||||
let h = hsl.h / 60.0;
|
g.clamp(0.0, 1.0),
|
||||||
let x = c * (1.0 - (h.rem_euclid(2.0) - 1.0).abs());
|
b.clamp(0.0, 1.0),
|
||||||
|
alpha,
|
||||||
let (r1, g1, b1) = if h < 1.0 {
|
)
|
||||||
(c, x, 0.0)
|
|
||||||
} else if h < 2.0 {
|
|
||||||
(x, c, 0.0)
|
|
||||||
} else if h < 3.0 {
|
|
||||||
(0.0, c, x)
|
|
||||||
} else if h < 4.0 {
|
|
||||||
(0.0, x, c)
|
|
||||||
} else if h < 5.0 {
|
|
||||||
(x, 0.0, c)
|
|
||||||
} else {
|
|
||||||
// h < 6.0
|
|
||||||
(c, 0.0, x)
|
|
||||||
};
|
|
||||||
|
|
||||||
let m = hsl.l - (c / 2.0);
|
|
||||||
|
|
||||||
Color {
|
|
||||||
r: r1 + m,
|
|
||||||
g: g1 + m,
|
|
||||||
b: b1 + m,
|
|
||||||
a: hsl.a,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -441,7 +441,7 @@ pub fn primary(theme: &Theme) -> Style {
|
||||||
/// Text conveying some secondary information, like a footnote.
|
/// Text conveying some secondary information, like a footnote.
|
||||||
pub fn secondary(theme: &Theme) -> Style {
|
pub fn secondary(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
color: Some(theme.extended_palette().secondary.strong.color),
|
color: Some(theme.extended_palette().secondary.base.color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -452,6 +452,13 @@ pub fn success(theme: &Theme) -> Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Text conveying some mildly negative information, like a warning.
|
||||||
|
pub fn warning(theme: &Theme) -> Style {
|
||||||
|
Style {
|
||||||
|
color: Some(theme.palette().warning),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Text conveying some negative information, like an error.
|
/// Text conveying some negative information, like an error.
|
||||||
pub fn danger(theme: &Theme) -> Style {
|
pub fn danger(theme: &Theme) -> Style {
|
||||||
Style {
|
Style {
|
||||||
|
|
|
||||||
|
|
@ -425,7 +425,7 @@ where
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = row![view].push_maybe(sidebar);
|
let content = row![view, sidebar];
|
||||||
|
|
||||||
themer(
|
themer(
|
||||||
theme,
|
theme,
|
||||||
|
|
|
||||||
|
|
@ -369,6 +369,7 @@ impl Pipeline {
|
||||||
color_attachments: &[Some(
|
color_attachments: &[Some(
|
||||||
wgpu::RenderPassColorAttachment {
|
wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: target,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
@ -568,6 +569,7 @@ impl DepthPipeline {
|
||||||
label: Some("cubes.pipeline.depth_pass"),
|
label: Some("cubes.pipeline.depth_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: target,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
|
||||||
|
|
@ -119,10 +119,10 @@ impl Editor {
|
||||||
|
|
||||||
let mut text = self.content.text();
|
let mut text = self.content.text();
|
||||||
|
|
||||||
if let Some(ending) = self.content.line_ending() {
|
if let Some(ending) = self.content.line_ending()
|
||||||
if !text.ends_with(ending.as_str()) {
|
&& !text.ends_with(ending.as_str())
|
||||||
text.push_str(ending.as_str());
|
{
|
||||||
}
|
text.push_str(ending.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::perform(
|
Task::perform(
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ impl Image {
|
||||||
.get("https://civitai.com/api/v1/images")
|
.get("https://civitai.com/api/v1/images")
|
||||||
.query(&[
|
.query(&[
|
||||||
("sort", "Most Reactions"),
|
("sort", "Most Reactions"),
|
||||||
("period", "Week"),
|
("period", "Month"),
|
||||||
("nsfw", "None"),
|
("nsfw", "None"),
|
||||||
("limit", &Image::LIMIT.to_string()),
|
("limit", &Image::LIMIT.to_string()),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use iced::animation;
|
||||||
use iced::time::{Instant, milliseconds};
|
use iced::time::{Instant, milliseconds};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, container, float, grid, horizontal_space, image, mouse_area,
|
button, container, float, grid, horizontal_space, image, mouse_area,
|
||||||
opaque, pop, scrollable, stack,
|
opaque, scrollable, sensor, stack,
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
|
|
@ -257,7 +257,7 @@ fn card<'a>(
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
pop(card)
|
sensor(card)
|
||||||
.on_show(|_| Message::ImagePoppedIn(metadata.id))
|
.on_show(|_| Message::ImagePoppedIn(metadata.id))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,17 +96,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
let capabilities = surface.get_capabilities(&adapter);
|
let capabilities = surface.get_capabilities(&adapter);
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(
|
.request_device(&wgpu::DeviceDescriptor {
|
||||||
&wgpu::DeviceDescriptor {
|
label: None,
|
||||||
label: None,
|
required_features: adapter_features
|
||||||
required_features: adapter_features
|
& wgpu::Features::default(),
|
||||||
& wgpu::Features::default(),
|
required_limits: wgpu::Limits::default(),
|
||||||
required_limits: wgpu::Limits::default(),
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||||
memory_hints:
|
trace: wgpu::Trace::Off,
|
||||||
wgpu::MemoryHints::MemoryUsage,
|
})
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.expect("Request device");
|
.expect("Request device");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ impl Scene {
|
||||||
label: None,
|
label: None,
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: target,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear({
|
load: wgpu::LoadOp::Clear({
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use iced::highlighter;
|
||||||
use iced::time::{self, Instant, milliseconds};
|
use iced::time::{self, Instant, milliseconds};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
self, button, center_x, container, horizontal_space, hover, image,
|
self, button, center_x, container, horizontal_space, hover, image,
|
||||||
markdown, pop, right, row, scrollable, text_editor, toggler,
|
markdown, right, row, scrollable, sensor, text_editor, toggler,
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
|
|
@ -267,7 +267,7 @@ impl<'a> markdown::Viewer<'a, Message> for CustomViewer<'a> {
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
pop(horizontal_space())
|
sensor(horizontal_space())
|
||||||
.key_ref(url.as_str())
|
.key_ref(url.as_str())
|
||||||
.delay(milliseconds(500))
|
.delay(milliseconds(500))
|
||||||
.on_show(|_size| Message::ImageShown(url.clone()))
|
.on_show(|_size| Message::ImageShown(url.clone()))
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,10 @@ impl Example {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::FocusAdjacent(direction) => {
|
Message::FocusAdjacent(direction) => {
|
||||||
if let Some(pane) = self.focus {
|
if let Some(pane) = self.focus
|
||||||
if let Some(adjacent) = self.panes.adjacent(pane, direction)
|
&& let Some(adjacent) = self.panes.adjacent(pane, direction)
|
||||||
{
|
{
|
||||||
self.focus = Some(adjacent);
|
self.focus = Some(adjacent);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Clicked(pane) => {
|
Message::Clicked(pane) => {
|
||||||
|
|
@ -106,14 +105,12 @@ impl Example {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::CloseFocused => {
|
Message::CloseFocused => {
|
||||||
if let Some(pane) = self.focus {
|
if let Some(pane) = self.focus
|
||||||
if let Some(Pane { is_pinned, .. }) = self.panes.get(pane) {
|
&& let Some(Pane { is_pinned, .. }) = self.panes.get(pane)
|
||||||
if !is_pinned {
|
&& !is_pinned
|
||||||
if let Some((_, sibling)) = self.panes.close(pane) {
|
&& let Some((_, sibling)) = self.panes.close(pane)
|
||||||
self.focus = Some(sibling);
|
{
|
||||||
}
|
self.focus = Some(sibling);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -276,13 +273,13 @@ fn view_content<'a>(
|
||||||
button(
|
button(
|
||||||
"Split vertically",
|
"Split vertically",
|
||||||
Message::Split(pane_grid::Axis::Vertical, pane),
|
Message::Split(pane_grid::Axis::Vertical, pane),
|
||||||
)
|
),
|
||||||
|
if total_panes > 1 && !is_pinned {
|
||||||
|
Some(button("Close", Message::Close(pane)).style(button::danger))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
]
|
]
|
||||||
.push_maybe(if total_panes > 1 && !is_pinned {
|
|
||||||
Some(button("Close", Message::Close(pane)).style(button::danger))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
.max_width(160);
|
.max_width(160);
|
||||||
|
|
||||||
|
|
@ -300,7 +297,7 @@ fn view_controls<'a>(
|
||||||
is_pinned: bool,
|
is_pinned: bool,
|
||||||
is_maximized: bool,
|
is_maximized: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let row = row![].spacing(5).push_maybe(if total_panes > 1 {
|
let maximize = if total_panes > 1 {
|
||||||
let (content, message) = if is_maximized {
|
let (content, message) = if is_maximized {
|
||||||
("Restore", Message::Restore)
|
("Restore", Message::Restore)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -315,7 +312,7 @@ fn view_controls<'a>(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
});
|
};
|
||||||
|
|
||||||
let close = button(text("Close").size(14))
|
let close = button(text("Close").size(14))
|
||||||
.style(button::danger)
|
.style(button::danger)
|
||||||
|
|
@ -326,7 +323,7 @@ fn view_controls<'a>(
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
|
|
||||||
row.push(close).into()
|
row![maximize, close].spacing(5).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
mod style {
|
mod style {
|
||||||
|
|
|
||||||
|
|
@ -88,18 +88,18 @@ impl QRGenerator {
|
||||||
input,
|
input,
|
||||||
row![toggle_total_size, choose_theme]
|
row![toggle_total_size, choose_theme]
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.align_y(Center)
|
.align_y(Center),
|
||||||
|
self.total_size.map(|total_size| {
|
||||||
|
slider(Self::SIZE_RANGE, total_size, Message::TotalSizeChanged)
|
||||||
|
}),
|
||||||
|
self.qr_code.as_ref().map(|data| {
|
||||||
|
if let Some(total_size) = self.total_size {
|
||||||
|
qr_code(data).total_size(total_size)
|
||||||
|
} else {
|
||||||
|
qr_code(data).cell_size(10.0)
|
||||||
|
}
|
||||||
|
})
|
||||||
]
|
]
|
||||||
.push_maybe(self.total_size.map(|total_size| {
|
|
||||||
slider(Self::SIZE_RANGE, total_size, Message::TotalSizeChanged)
|
|
||||||
}))
|
|
||||||
.push_maybe(self.qr_code.as_ref().map(|data| {
|
|
||||||
if let Some(total_size) = self.total_size {
|
|
||||||
qr_code(data).total_size(total_size)
|
|
||||||
} else {
|
|
||||||
qr_code(data).cell_size(10.0)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.width(700)
|
.width(700)
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.align_x(Center);
|
.align_x(Center);
|
||||||
|
|
|
||||||
|
|
@ -158,15 +158,15 @@ impl Example {
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_y(Center);
|
.align_y(Center);
|
||||||
|
|
||||||
let crop_controls =
|
let crop_controls = column![
|
||||||
column![crop_origin_controls, crop_dimension_controls]
|
crop_origin_controls,
|
||||||
.push_maybe(
|
crop_dimension_controls,
|
||||||
self.crop_error
|
self.crop_error
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|error| text!("Crop error! \n{error}")),
|
.map(|error| text!("Crop error! \n{error}")),
|
||||||
)
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_x(Center);
|
.align_x(Center);
|
||||||
|
|
||||||
let controls = {
|
let controls = {
|
||||||
let save_result =
|
let save_result =
|
||||||
|
|
@ -208,8 +208,8 @@ impl Example {
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_x(Center),
|
.align_x(Center),
|
||||||
|
save_result.map(text)
|
||||||
]
|
]
|
||||||
.push_maybe(save_result.map(text))
|
|
||||||
.spacing(40)
|
.spacing(40)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
30570747bb062e9f7730cdd58be961c84bcf4711a6983185bff6d903e8d29e9c
|
0650eb2c27c21c5d48e1e00031a52d8471d8a3b4e827ad502c4628914f5c1c13
|
||||||
|
|
@ -1 +1 @@
|
||||||
d5a086a08544f98087189bd4ece8815e5290722a07cd580b933f1bf77a040c52
|
ae1da92064373838152ac163072ee68135f530e0fef8146a01aea1df5cfdb494
|
||||||
|
|
@ -1 +1 @@
|
||||||
30e523961db89a3ee97ad1eac09e727ecb3dec485faa362534a9f5ad083b32dd
|
8466dc0975c0bc7c06ed3c45df51e99b9d384394f8c3689b15231a872ba1262f
|
||||||
|
|
@ -1 +1 @@
|
||||||
bce5427d5105f68e1d7fa18a34fcc551cb78c2fefd9a583ba44686331133436d
|
1c8f13cfb5d0bbbeb24b80ed35671e8a93d208d79ac4dbd069fe65c4a53c50c2
|
||||||
|
|
@ -1 +1 @@
|
||||||
c8a7edbd5a8bbf559134b84253e14e65340f4ffe3e22c272b21c8438e47ffaf7
|
a1d30652db2cce98b5b86e8e29d776e2fc9091056aff8861cd54fa061161ed47
|
||||||
|
|
@ -1 +1 @@
|
||||||
63d646b22d3dffbb56dac2e3f345090bd26625a388dd6cc142359f2a7ac9c8df
|
8c01615169803510f1cd4d051721b415adc7147672238aff1275fa3741edb507
|
||||||
|
|
@ -1 +1 @@
|
||||||
d26f55674cbd96bc3b534ffdd098a13199718ef9c5ffe8ece0882ddab714b776
|
0b10823a1d218c145214ff2dcf751584669a3ca1d3e777a2cd618479a809523e
|
||||||
|
|
@ -1 +1 @@
|
||||||
482c44c13d4ff3de19e71f3dddf93bbee170e54e2d353e818811069de28e18ed
|
26bc668c55650c6c25a14f76feeb1d1f78a96835aaac7a5f57b48b838cb28b14
|
||||||
|
|
@ -1 +1 @@
|
||||||
6738cc4fc6eb8a5d406c613a4b0f08c0e8dcd2c1a5444445eebd3888f9303841
|
228eb8d64eed2f3726d27490b88a4519e36979a0ccfb0db8e164c5e5296b0739
|
||||||
|
|
@ -1 +1 @@
|
||||||
0a918c52538fc4848aa0c68d8f2d6f4c981ed68971dd9c725f0093a39ef7f353
|
d579b14db1650e907f925302f23c53ebaba370aef6410cfa48fef70ab3138d1f
|
||||||
|
|
@ -1 +1 @@
|
||||||
de3e1a2c21e1a86d76ca99989c73e8a2596ef627bba95d246fab8f02d56bd0af
|
896072b46221f83e1edaa37574436af6474969625f5c1a41cc5ddc2e20823cee
|
||||||
|
|
@ -1 +1 @@
|
||||||
3418ea4eb0f7786607ef02e7db4bc97309530f2f7c08f8aea15c768a13a09ca4
|
60e1c95159caddb8bd7b8360e32ffd75472be37c4fcbd8ad23dabd0d000a4ec1
|
||||||
|
|
@ -1 +1 @@
|
||||||
c8474e02a9df23f123816a489c1ea7ae6cb994a0eca429592dfe6d933de1beee
|
2e3c4ea86b5bd968b8ec77a7ec7b5b7ae29d5ee8e4b68a216c1fa11d92c015bc
|
||||||
|
|
@ -1 +1 @@
|
||||||
02095fd09c078be02dc41e29e55de25e8a79e6ad4293aa7e430257a9016dfb3d
|
79ffced2a78689bac1a40ab154a478b4fff87154ee4a8bbf023d922c86b7d53b
|
||||||
|
|
@ -1 +1 @@
|
||||||
d82588a2aba3e7211f25b85ebb812a42dfa59137dd4b59d26f5f60d5b28e537f
|
17c632cbef607502ed2c438f409a1c9bee382d9084c38772021f1f2a4ad3908c
|
||||||
|
|
@ -1 +1 @@
|
||||||
d6b73545929cc7794c1a918f069b5326ef129bed8f9ad2cd001be7d078a2b6a0
|
359f7f2c1d7f87e6e0eb80a9a28f70f033d6321ba028d32bc372030b718ed481
|
||||||
|
|
@ -1 +1 @@
|
||||||
0ec7251c69755becd678b7aec398a275edf31cc077960723cd6b9364e8678548
|
a908d8f154f2baf67455380b5d8b39003c08ba0c80f39e71d4bcd2377bc784fc
|
||||||
|
|
@ -1 +1 @@
|
||||||
4a15c475d45cf8eb0ccd6727cf6e493bd8c22454610b167a632a2328308faed1
|
8d6c2bab1f6e9a8db1e2acc8eb76334170e046b709a36dd4ad4d86f8d47346a4
|
||||||
|
|
@ -1 +1 @@
|
||||||
49a41af93e89aab0a4e352e9cedfba3c6e18caf4267955c9d362bad40264a165
|
2010df2e80bfc72e7e9274de07b77dc4843485f6be38266fdfb7a4f129d75da1
|
||||||
|
|
@ -1 +1 @@
|
||||||
8fcd80d4569dafdac4b4452b8ca8ab0cdceeb755f3c83d374ccd5ed4d0e8d43d
|
74812d50467787ce39a33ad6bc89411d7b8bc0b13e1bbd45838fcc27c75aee98
|
||||||
|
|
@ -1 +1 @@
|
||||||
c37a32784c769c046f3aa881914b121af373b8c6e175ced89304d15b626a653a
|
b04218ee65cd446b142596a2cd9ff69d5267969af86026a4ff394f3c13a4d842
|
||||||
|
|
@ -1 +1 @@
|
||||||
533d25575e8bf1111036fb082b424d0d0e60947a7da8428ab8c71e0bda01469e
|
e1cbe8742f000921c86924056e9a45f95ee2a2a973743bf9f37fee65baccfb9b
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use iced::keyboard;
|
use iced::keyboard;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, center, checkbox, column, container, horizontal_rule, pick_list,
|
button, center_x, center_y, checkbox, column, container, horizontal_rule,
|
||||||
progress_bar, row, scrollable, slider, text, text_input, toggler,
|
pick_list, progress_bar, row, scrollable, slider, text, text_input,
|
||||||
vertical_rule, vertical_space,
|
toggler, vertical_rule, vertical_space,
|
||||||
};
|
};
|
||||||
use iced::{Center, Element, Fill, Subscription, Theme};
|
use iced::{Center, Element, Fill, Shrink, Subscription, Theme};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(Styling::default, Styling::update, Styling::view)
|
iced::application(Styling::default, Styling::update, Styling::view)
|
||||||
|
|
@ -78,38 +78,64 @@ impl Styling {
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.size(20);
|
.size(20);
|
||||||
|
|
||||||
let styled_button = |label| {
|
let buttons = {
|
||||||
button(text(label).width(Fill).center())
|
let styles = [
|
||||||
.padding(10)
|
("Primary", button::primary as fn(&Theme, _) -> _),
|
||||||
.on_press(Message::ButtonPressed)
|
("Secondary", button::secondary),
|
||||||
};
|
("Success", button::success),
|
||||||
|
("Warning", button::warning),
|
||||||
|
("Danger", button::danger),
|
||||||
|
];
|
||||||
|
|
||||||
let primary = styled_button("Primary");
|
let styled_button =
|
||||||
let success = styled_button("Success").style(button::success);
|
|label| button(text(label).width(Fill).center()).padding(10);
|
||||||
let warning = styled_button("Warning").style(button::warning);
|
|
||||||
let danger = styled_button("Danger").style(button::danger);
|
column![
|
||||||
|
row(styles.into_iter().map(|(name, style)| styled_button(
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.on_press(Message::ButtonPressed)
|
||||||
|
.style(style)
|
||||||
|
.into()))
|
||||||
|
.spacing(10)
|
||||||
|
.align_y(Center),
|
||||||
|
row(styles.into_iter().map(|(name, style)| styled_button(
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.style(style)
|
||||||
|
.into()))
|
||||||
|
.spacing(10)
|
||||||
|
.align_y(Center),
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
};
|
||||||
|
|
||||||
let slider =
|
let slider =
|
||||||
|| slider(0.0..=100.0, self.slider_value, Message::SliderChanged);
|
|| slider(0.0..=100.0, self.slider_value, Message::SliderChanged);
|
||||||
|
|
||||||
let progress_bar = || progress_bar(0.0..=100.0, self.slider_value);
|
let progress_bar = || progress_bar(0.0..=100.0, self.slider_value);
|
||||||
|
|
||||||
let scrollable = scrollable(column![
|
let scroll_me = scrollable(column![
|
||||||
"Scroll me!",
|
"Scroll me!",
|
||||||
vertical_space().height(800),
|
vertical_space().height(800),
|
||||||
"You did it!"
|
"You did it!"
|
||||||
])
|
])
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(100);
|
.height(Fill);
|
||||||
|
|
||||||
let checkbox = checkbox("Check me!", self.checkbox_value)
|
let check = checkbox("Check me!", self.checkbox_value)
|
||||||
.on_toggle(Message::CheckboxToggled);
|
.on_toggle(Message::CheckboxToggled);
|
||||||
|
|
||||||
let toggler = toggler(self.toggler_value)
|
let check_disabled = checkbox("Disabled", self.checkbox_value);
|
||||||
|
|
||||||
|
let toggle = toggler(self.toggler_value)
|
||||||
.label("Toggle me!")
|
.label("Toggle me!")
|
||||||
.on_toggle(Message::TogglerToggled)
|
.on_toggle(Message::TogglerToggled)
|
||||||
.spacing(10);
|
.spacing(10);
|
||||||
|
|
||||||
|
let disabled_toggle =
|
||||||
|
toggler(self.toggler_value).label("Disabled").spacing(10);
|
||||||
|
|
||||||
let card = {
|
let card = {
|
||||||
container(
|
container(
|
||||||
column![
|
column![
|
||||||
|
|
@ -128,18 +154,17 @@ impl Styling {
|
||||||
choose_theme,
|
choose_theme,
|
||||||
horizontal_rule(1),
|
horizontal_rule(1),
|
||||||
text_input,
|
text_input,
|
||||||
row![primary, success, warning, danger]
|
buttons,
|
||||||
.spacing(10)
|
|
||||||
.align_y(Center),
|
|
||||||
slider(),
|
slider(),
|
||||||
progress_bar(),
|
progress_bar(),
|
||||||
row![
|
row![
|
||||||
scrollable,
|
scroll_me,
|
||||||
row![vertical_rule(1), column![checkbox, toggler].spacing(20)]
|
vertical_rule(1),
|
||||||
.spacing(20)
|
column![check, check_disabled, toggle, disabled_toggle]
|
||||||
|
.spacing(10)
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.height(100)
|
.height(Shrink)
|
||||||
.align_y(Center),
|
.align_y(Center),
|
||||||
card
|
card
|
||||||
]
|
]
|
||||||
|
|
@ -147,7 +172,9 @@ impl Styling {
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.max_width(600);
|
.max_width(600);
|
||||||
|
|
||||||
center(content).into()
|
center_y(scrollable(center_x(content)).spacing(10))
|
||||||
|
.padding(10)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
|
|
||||||
10
examples/table/Cargo.toml
Normal file
10
examples/table/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "table"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
|
edition = "2024"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced.workspace = true
|
||||||
|
iced.features = ["debug"]
|
||||||
252
examples/table/src/main.rs
Normal file
252
examples/table/src/main.rs
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
use iced::font;
|
||||||
|
use iced::time::{Duration, hours, minutes};
|
||||||
|
use iced::widget::{
|
||||||
|
center_x, center_y, column, container, row, scrollable, slider, table,
|
||||||
|
text, tooltip,
|
||||||
|
};
|
||||||
|
use iced::{Center, Element, Fill, Font, Right, Theme};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
iced::application(Table::new, Table::update, Table::view)
|
||||||
|
.theme(|_| Theme::CatppuccinMocha)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Table {
|
||||||
|
events: Vec<Event>,
|
||||||
|
padding: (f32, f32),
|
||||||
|
separator: (f32, f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
PaddingChanged(f32, f32),
|
||||||
|
SeparatorChanged(f32, f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Table {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
events: Event::list(),
|
||||||
|
padding: (10.0, 5.0),
|
||||||
|
separator: (1.0, 1.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
match message {
|
||||||
|
Message::PaddingChanged(x, y) => self.padding = (x, y),
|
||||||
|
Message::SeparatorChanged(x, y) => self.separator = (x, y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<'_, Message> {
|
||||||
|
let table = {
|
||||||
|
let bold = |header| {
|
||||||
|
text(header).font(Font {
|
||||||
|
weight: font::Weight::Bold,
|
||||||
|
..Font::DEFAULT
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let columns = [
|
||||||
|
table::column(bold("Name"), |event: &Event| text(&event.name)),
|
||||||
|
table::column(bold("Time"), |event: &Event| {
|
||||||
|
let minutes = event.duration.as_secs() / 60;
|
||||||
|
|
||||||
|
text!("{minutes} min").style(if minutes > 90 {
|
||||||
|
text::warning
|
||||||
|
} else {
|
||||||
|
text::default
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.align_x(Right)
|
||||||
|
.align_y(Center),
|
||||||
|
table::column(bold("Price"), |event: &Event| {
|
||||||
|
if event.price > 0.0 {
|
||||||
|
text!("${:.2}", event.price).style(
|
||||||
|
if event.price > 100.0 {
|
||||||
|
text::warning
|
||||||
|
} else {
|
||||||
|
text::default
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
text("Free").style(text::success).width(Fill).center()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.align_x(Right)
|
||||||
|
.align_y(Center),
|
||||||
|
table::column(bold("Rating"), |event: &Event| {
|
||||||
|
text!("{:.2}", event.rating).style(if event.rating > 4.7 {
|
||||||
|
text::success
|
||||||
|
} else if event.rating < 2.0 {
|
||||||
|
text::danger
|
||||||
|
} else {
|
||||||
|
text::default
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.align_x(Right)
|
||||||
|
.align_y(Center),
|
||||||
|
];
|
||||||
|
|
||||||
|
table(columns, &self.events)
|
||||||
|
.padding_x(self.padding.0)
|
||||||
|
.padding_y(self.padding.1)
|
||||||
|
.separator_x(self.separator.0)
|
||||||
|
.separator_y(self.separator.1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let controls = {
|
||||||
|
let labeled_slider =
|
||||||
|
|label,
|
||||||
|
range: std::ops::RangeInclusive<f32>,
|
||||||
|
(x, y),
|
||||||
|
on_change: fn(f32, f32) -> Message| {
|
||||||
|
row![
|
||||||
|
text(label).font(Font::MONOSPACE).size(14).width(100),
|
||||||
|
tooltip(
|
||||||
|
slider(range.clone(), x, move |x| on_change(x, y)),
|
||||||
|
text!("{x:.0}px").font(Font::MONOSPACE).size(10),
|
||||||
|
tooltip::Position::Left
|
||||||
|
),
|
||||||
|
tooltip(
|
||||||
|
slider(range, y, move |y| on_change(x, y)),
|
||||||
|
text!("{y:.0}px").font(Font::MONOSPACE).size(10),
|
||||||
|
tooltip::Position::Right
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.align_y(Center)
|
||||||
|
};
|
||||||
|
|
||||||
|
column![
|
||||||
|
labeled_slider(
|
||||||
|
"Padding",
|
||||||
|
0.0..=30.0,
|
||||||
|
self.padding,
|
||||||
|
Message::PaddingChanged
|
||||||
|
),
|
||||||
|
labeled_slider(
|
||||||
|
"Separator",
|
||||||
|
0.0..=5.0,
|
||||||
|
self.separator,
|
||||||
|
Message::SeparatorChanged
|
||||||
|
)
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.width(400)
|
||||||
|
};
|
||||||
|
|
||||||
|
column![
|
||||||
|
center_y(scrollable(center_x(table)).spacing(10)).padding(10),
|
||||||
|
center_x(controls).padding(10).style(container::dark)
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
name: String,
|
||||||
|
duration: Duration,
|
||||||
|
price: f32,
|
||||||
|
rating: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
fn list() -> Vec<Self> {
|
||||||
|
vec![
|
||||||
|
Event {
|
||||||
|
name: "Get lost in a hacker bookstore".to_owned(),
|
||||||
|
duration: hours(2),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.9,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Buy vintage synth at Noisebridge flea market".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 150.0,
|
||||||
|
rating: 4.8,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Eat a questionable hot dog at 2AM".to_owned(),
|
||||||
|
duration: minutes(20),
|
||||||
|
price: 5.0,
|
||||||
|
rating: 1.7,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Ride the MUNI for the story".to_owned(),
|
||||||
|
duration: minutes(60),
|
||||||
|
price: 3.0,
|
||||||
|
rating: 4.1,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Scream into the void from Twin Peaks".to_owned(),
|
||||||
|
duration: minutes(40),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.9,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Buy overpriced coffee and feel things".to_owned(),
|
||||||
|
duration: minutes(25),
|
||||||
|
price: 6.5,
|
||||||
|
rating: 4.5,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Attend an underground robot poetry slam".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 12.0,
|
||||||
|
rating: 4.8,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Browse cursed tech at a retro computer fair".to_owned(),
|
||||||
|
duration: hours(2),
|
||||||
|
price: 10.0,
|
||||||
|
rating: 4.7,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Try to order at a secret ramen place with no sign"
|
||||||
|
.to_owned(),
|
||||||
|
duration: minutes(50),
|
||||||
|
price: 14.0,
|
||||||
|
rating: 4.6,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Join a spontaneous rooftop drone rave".to_owned(),
|
||||||
|
duration: hours(3),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.9,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Sketch a stranger at Dolores Park".to_owned(),
|
||||||
|
duration: minutes(45),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.4,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Visit the Museum of Obsolete APIs".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 9.99,
|
||||||
|
rating: 4.2,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Chase the last working payphone".to_owned(),
|
||||||
|
duration: minutes(35),
|
||||||
|
price: 0.25,
|
||||||
|
rating: 4.0,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Trade zines with a punk on BART".to_owned(),
|
||||||
|
duration: minutes(30),
|
||||||
|
price: 3.5,
|
||||||
|
rating: 4.7,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Get a tattoo of the Git logo".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 200.0,
|
||||||
|
rating: 4.6,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
0e355b080ad33905145e9f70a3b29e2481197c8fc8f42491acd5358238ebbd5f
|
99f418007af163f172e163565f166da31015521e1bf7de95fa55cda2fb5a7db5
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
804a1bb6d49e3b3158463202960447d9e7820b967280f41dd0c34c00d3edf2c3
|
|
||||||
|
|
@ -142,17 +142,17 @@ impl Tour {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<'_, Message> {
|
fn view(&self) -> Element<'_, Message> {
|
||||||
let controls =
|
let controls = row![
|
||||||
row![]
|
self.screen.previous().is_some().then(|| {
|
||||||
.push_maybe(self.screen.previous().is_some().then(|| {
|
padded_button("Back")
|
||||||
padded_button("Back")
|
.on_press(Message::BackPressed)
|
||||||
.on_press(Message::BackPressed)
|
.style(button::secondary)
|
||||||
.style(button::secondary)
|
}),
|
||||||
}))
|
horizontal_space(),
|
||||||
.push(horizontal_space())
|
self.can_continue().then(|| {
|
||||||
.push_maybe(self.can_continue().then(|| {
|
padded_button("Next").on_press(Message::NextPressed)
|
||||||
padded_button("Next").on_press(Message::NextPressed)
|
})
|
||||||
}));
|
];
|
||||||
|
|
||||||
let screen = match self.screen {
|
let screen = match self.screen {
|
||||||
Screen::Welcome => self.welcome(),
|
Screen::Welcome => self.welcome(),
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,11 @@ impl WebSocket {
|
||||||
let mut button = button(text("Send").height(40).align_y(Center))
|
let mut button = button(text("Send").height(40).align_y(Center))
|
||||||
.padding([0, 20]);
|
.padding([0, 20]);
|
||||||
|
|
||||||
if matches!(self.state, State::Connected(_)) {
|
if matches!(self.state, State::Connected(_))
|
||||||
if let Some(message) = echo::Message::new(&self.new_message) {
|
&& let Some(message) = echo::Message::new(&self.new_message)
|
||||||
input = input.on_submit(Message::Send(message.clone()));
|
{
|
||||||
button = button.on_press(Message::Send(message));
|
input = input.on_submit(Message::Send(message.clone()));
|
||||||
}
|
button = button.on_press(Message::Send(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
row![input, button].spacing(10).align_y(Center)
|
row![input, button].spacing(10).align_y(Center)
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ impl FontSystem {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct Version(u32);
|
pub struct Version(u32);
|
||||||
|
|
||||||
/// A weak reference to a [`cosmic-text::Buffer`] that can be drawn.
|
/// A weak reference to a [`cosmic_text::Buffer`] that can be drawn.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Raw {
|
pub struct Raw {
|
||||||
/// A weak reference to a [`cosmic_text::Buffer`].
|
/// A weak reference to a [`cosmic_text::Buffer`].
|
||||||
|
|
|
||||||
|
|
@ -321,10 +321,11 @@ impl editor::Editor for Editor {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Deselect if selection matches cursor position
|
// Deselect if selection matches cursor position
|
||||||
if let Some((start, end)) = editor.selection_bounds() {
|
if let Some((start, end)) = editor.selection_bounds()
|
||||||
if start.line == end.line && start.index == end.index {
|
&& start.line == end.line
|
||||||
editor.set_selection(cosmic_text::Selection::None);
|
&& start.index == end.index
|
||||||
}
|
{
|
||||||
|
editor.set_selection(cosmic_text::Selection::None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::SelectWord => {
|
Action::SelectWord => {
|
||||||
|
|
@ -438,10 +439,11 @@ impl editor::Editor for Editor {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Deselect if selection matches cursor position
|
// Deselect if selection matches cursor position
|
||||||
if let Some((start, end)) = editor.selection_bounds() {
|
if let Some((start, end)) = editor.selection_bounds()
|
||||||
if start.line == end.line && start.index == end.index {
|
&& start.line == end.line
|
||||||
editor.set_selection(cosmic_text::Selection::None);
|
&& start.index == end.index
|
||||||
}
|
{
|
||||||
|
editor.set_selection(cosmic_text::Selection::None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::Scroll { lines } => {
|
Action::Scroll { lines } => {
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ pub fn convert(
|
||||||
label: Some("iced_wgpu.offscreen.blit.render_pass"),
|
label: Some("iced_wgpu.offscreen.blit.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view,
|
view,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,8 @@ impl Cache {
|
||||||
self.map.retain(|k, memory| {
|
self.map.retain(|k, memory| {
|
||||||
let retain = hits.contains(k);
|
let retain = hits.contains(k);
|
||||||
|
|
||||||
if !retain {
|
if !retain && let Memory::Device(entry) = memory {
|
||||||
if let Memory::Device(entry) = memory {
|
atlas.remove(entry);
|
||||||
atlas.remove(entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
retain
|
retain
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ impl Renderer {
|
||||||
let _ = self
|
let _ = self
|
||||||
.engine
|
.engine
|
||||||
.device
|
.device
|
||||||
.poll(wgpu::Maintain::WaitForSubmissionIndex(index));
|
.poll(wgpu::PollType::WaitForSubmissionIndex(index));
|
||||||
|
|
||||||
let mapped_buffer = slice.get_mapped_range();
|
let mapped_buffer = slice.get_mapped_range();
|
||||||
|
|
||||||
|
|
@ -426,6 +426,7 @@ impl Renderer {
|
||||||
label: Some("iced_wgpu render pass"),
|
label: Some("iced_wgpu render pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: frame,
|
view: frame,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: match clear_color {
|
load: match clear_color {
|
||||||
|
|
@ -514,6 +515,7 @@ impl Renderer {
|
||||||
color_attachments: &[Some(
|
color_attachments: &[Some(
|
||||||
wgpu::RenderPassColorAttachment {
|
wgpu::RenderPassColorAttachment {
|
||||||
view: frame,
|
view: frame,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
@ -560,6 +562,7 @@ impl Renderer {
|
||||||
color_attachments: &[Some(
|
color_attachments: &[Some(
|
||||||
wgpu::RenderPassColorAttachment {
|
wgpu::RenderPassColorAttachment {
|
||||||
view: frame,
|
view: frame,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
@ -830,21 +833,20 @@ impl renderer::Headless for Renderer {
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
compatible_surface: None,
|
compatible_surface: None,
|
||||||
})
|
})
|
||||||
.await?;
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(
|
.request_device(&wgpu::DeviceDescriptor {
|
||||||
&wgpu::DeviceDescriptor {
|
label: Some("iced_wgpu [headless]"),
|
||||||
label: Some("iced_wgpu [headless]"),
|
required_features: wgpu::Features::empty(),
|
||||||
required_features: wgpu::Features::empty(),
|
required_limits: wgpu::Limits {
|
||||||
required_limits: wgpu::Limits {
|
max_bind_groups: 2,
|
||||||
max_bind_groups: 2,
|
..wgpu::Limits::default()
|
||||||
..wgpu::Limits::default()
|
|
||||||
},
|
|
||||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
|
||||||
},
|
},
|
||||||
None,
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||||
)
|
trace: wgpu::Trace::Off,
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -335,6 +335,7 @@ fn render<'a>(
|
||||||
label: Some("iced_wgpu.triangle.render_pass"),
|
label: Some("iced_wgpu.triangle.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: target,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ impl Pipeline {
|
||||||
label: Some("iced_wgpu.triangle.render_pass"),
|
label: Some("iced_wgpu.triangle.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: &targets.attachment,
|
view: &targets.attachment,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: Some(&targets.resolve),
|
resolve_target: Some(&targets.resolve),
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
|
|
@ -340,6 +341,7 @@ impl State {
|
||||||
label: Some("iced_wgpu::triangle::msaa render pass"),
|
label: Some("iced_wgpu::triangle::msaa render pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: target,
|
||||||
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Load,
|
load: wgpu::LoadOp::Load,
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,10 @@ impl Compositor {
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let adapter = instance
|
let adapter =
|
||||||
.request_adapter(&adapter_options)
|
instance.request_adapter(&adapter_options).await.map_err(
|
||||||
.await
|
|_error| Error::NoAdapterFound(format!("{adapter_options:?}")),
|
||||||
.ok_or(Error::NoAdapterFound(format!("{adapter_options:?}")))?;
|
)?;
|
||||||
|
|
||||||
log::info!("Selected: {:#?}", adapter.get_info());
|
log::info!("Selected: {:#?}", adapter.get_info());
|
||||||
|
|
||||||
|
|
@ -162,17 +162,15 @@ impl Compositor {
|
||||||
|
|
||||||
for required_limits in limits {
|
for required_limits in limits {
|
||||||
let result = adapter
|
let result = adapter
|
||||||
.request_device(
|
.request_device(&wgpu::DeviceDescriptor {
|
||||||
&wgpu::DeviceDescriptor {
|
label: Some(
|
||||||
label: Some(
|
"iced_wgpu::window::compositor device descriptor",
|
||||||
"iced_wgpu::window::compositor device descriptor",
|
),
|
||||||
),
|
required_features: wgpu::Features::empty(),
|
||||||
required_features: wgpu::Features::empty(),
|
required_limits: required_limits.clone(),
|
||||||
required_limits: required_limits.clone(),
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
trace: wgpu::Trace::Off,
|
||||||
},
|
})
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
fn styled(pair: palette::Pair) -> Style {
|
||||||
Style {
|
Style {
|
||||||
background: Some(Background::Color(pair.color)),
|
background: Some(Background::Color(pair.color)),
|
||||||
|
|
|
||||||
|
|
@ -320,11 +320,9 @@ where
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let mouse_over = cursor.is_over(layout.bounds());
|
let mouse_over = cursor.is_over(layout.bounds());
|
||||||
|
|
||||||
if mouse_over {
|
if mouse_over && let Some(on_toggle) = &self.on_toggle {
|
||||||
if let Some(on_toggle) = &self.on_toggle {
|
shell.publish((on_toggle)(!self.is_checked));
|
||||||
shell.publish((on_toggle)(!self.is_checked));
|
shell.capture_event();
|
||||||
shell.capture_event();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -556,23 +554,23 @@ pub fn primary(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Active { is_checked } => styled(
|
Status::Active { is_checked } => styled(
|
||||||
palette.primary.strong.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.base,
|
palette.background.base,
|
||||||
|
palette.primary.base.text,
|
||||||
palette.primary.base,
|
palette.primary.base,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Hovered { is_checked } => styled(
|
Status::Hovered { is_checked } => styled(
|
||||||
palette.primary.strong.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.primary.base.text,
|
||||||
palette.primary.strong,
|
palette.primary.strong,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Disabled { is_checked } => styled(
|
Status::Disabled { is_checked } => styled(
|
||||||
palette.primary.strong.text,
|
|
||||||
palette.background.weak.color,
|
palette.background.weak.color,
|
||||||
palette.background.weak,
|
palette.background.weaker,
|
||||||
|
palette.primary.base.text,
|
||||||
palette.background.strong,
|
palette.background.strong,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
|
|
@ -585,23 +583,23 @@ pub fn secondary(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Active { is_checked } => styled(
|
Status::Active { is_checked } => styled(
|
||||||
palette.background.base.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.base,
|
palette.background.base,
|
||||||
|
palette.background.base.text,
|
||||||
palette.background.strong,
|
palette.background.strong,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Hovered { is_checked } => styled(
|
Status::Hovered { is_checked } => styled(
|
||||||
palette.background.base.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.background.base.text,
|
||||||
palette.background.strong,
|
palette.background.strong,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Disabled { is_checked } => styled(
|
Status::Disabled { is_checked } => styled(
|
||||||
palette.background.strong.color,
|
|
||||||
palette.background.weak.color,
|
palette.background.weak.color,
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.background.base.text,
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
|
|
@ -614,23 +612,23 @@ pub fn success(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Active { is_checked } => styled(
|
Status::Active { is_checked } => styled(
|
||||||
palette.success.base.text,
|
|
||||||
palette.background.weak.color,
|
palette.background.weak.color,
|
||||||
palette.background.base,
|
palette.background.base,
|
||||||
|
palette.success.base.text,
|
||||||
palette.success.base,
|
palette.success.base,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Hovered { is_checked } => styled(
|
Status::Hovered { is_checked } => styled(
|
||||||
palette.success.base.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.success.base.text,
|
||||||
palette.success.strong,
|
palette.success.strong,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Disabled { is_checked } => styled(
|
Status::Disabled { is_checked } => styled(
|
||||||
palette.success.base.text,
|
|
||||||
palette.background.weak.color,
|
palette.background.weak.color,
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.success.base.text,
|
||||||
palette.success.weak,
|
palette.success.weak,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
|
|
@ -643,23 +641,23 @@ pub fn danger(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Active { is_checked } => styled(
|
Status::Active { is_checked } => styled(
|
||||||
palette.danger.base.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.base,
|
palette.background.base,
|
||||||
|
palette.danger.base.text,
|
||||||
palette.danger.base,
|
palette.danger.base,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Hovered { is_checked } => styled(
|
Status::Hovered { is_checked } => styled(
|
||||||
palette.danger.base.text,
|
palette.background.strong.color,
|
||||||
palette.background.strongest.color,
|
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.danger.base.text,
|
||||||
palette.danger.strong,
|
palette.danger.strong,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
Status::Disabled { is_checked } => styled(
|
Status::Disabled { is_checked } => styled(
|
||||||
palette.danger.base.text,
|
|
||||||
palette.background.weak.color,
|
palette.background.weak.color,
|
||||||
palette.background.weak,
|
palette.background.weak,
|
||||||
|
palette.danger.base.text,
|
||||||
palette.danger.weak,
|
palette.danger.weak,
|
||||||
is_checked,
|
is_checked,
|
||||||
),
|
),
|
||||||
|
|
@ -667,27 +665,25 @@ pub fn danger(theme: &Theme, status: Status) -> Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled(
|
fn styled(
|
||||||
icon_color: Color,
|
|
||||||
border_color: Color,
|
border_color: Color,
|
||||||
base: palette::Pair,
|
base: palette::Pair,
|
||||||
|
icon_color: Color,
|
||||||
accent: palette::Pair,
|
accent: palette::Pair,
|
||||||
is_checked: bool,
|
is_checked: bool,
|
||||||
) -> Style {
|
) -> Style {
|
||||||
|
let (background, border) = if is_checked {
|
||||||
|
(accent, accent.color)
|
||||||
|
} else {
|
||||||
|
(base, border_color)
|
||||||
|
};
|
||||||
|
|
||||||
Style {
|
Style {
|
||||||
background: Background::Color(if is_checked {
|
background: Background::Color(background.color),
|
||||||
accent.color
|
|
||||||
} else {
|
|
||||||
base.color
|
|
||||||
}),
|
|
||||||
icon_color,
|
icon_color,
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: if is_checked {
|
color: border,
|
||||||
accent.color
|
|
||||||
} else {
|
|
||||||
border_color
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
text_color: None,
|
text_color: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,23 +145,13 @@ where
|
||||||
let child = child.into();
|
let child = child.into();
|
||||||
let child_size = child.as_widget().size_hint();
|
let child_size = child.as_widget().size_hint();
|
||||||
|
|
||||||
self.width = self.width.enclose(child_size.width);
|
if !child_size.is_void() {
|
||||||
self.height = self.height.enclose(child_size.height);
|
self.width = self.width.enclose(child_size.width);
|
||||||
|
self.height = self.height.enclose(child_size.height);
|
||||||
self.children.push(child);
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extends the [`Column`] with the given children.
|
/// Extends the [`Column`] with the given children.
|
||||||
|
|
|
||||||
|
|
@ -602,17 +602,16 @@ where
|
||||||
|
|
||||||
if is_focused {
|
if is_focused {
|
||||||
self.state.with_inner(|state| {
|
self.state.with_inner(|state| {
|
||||||
if !started_focused {
|
if !started_focused
|
||||||
if let Some(on_option_hovered) = &mut self.on_option_hovered
|
&& let Some(on_option_hovered) = &mut self.on_option_hovered
|
||||||
{
|
{
|
||||||
let hovered_option = menu.hovered_option.unwrap_or(0);
|
let hovered_option = menu.hovered_option.unwrap_or(0);
|
||||||
|
|
||||||
if let Some(option) =
|
if let Some(option) =
|
||||||
state.filtered_options.options.get(hovered_option)
|
state.filtered_options.options.get(hovered_option)
|
||||||
{
|
{
|
||||||
shell.publish(on_option_hovered(option.clone()));
|
shell.publish(on_option_hovered(option.clone()));
|
||||||
published_message_to_shell = true;
|
published_message_to_shell = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -625,12 +624,11 @@ where
|
||||||
let shift_modifier = modifiers.shift();
|
let shift_modifier = modifiers.shift();
|
||||||
match (named_key, shift_modifier) {
|
match (named_key, shift_modifier) {
|
||||||
(key::Named::Enter, _) => {
|
(key::Named::Enter, _) => {
|
||||||
if let Some(index) = &menu.hovered_option {
|
if let Some(index) = &menu.hovered_option
|
||||||
if let Some(option) =
|
&& let Some(option) =
|
||||||
state.filtered_options.options.get(*index)
|
state.filtered_options.options.get(*index)
|
||||||
{
|
{
|
||||||
menu.new_selection = Some(option.clone());
|
menu.new_selection = Some(option.clone());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
|
@ -653,21 +651,19 @@ where
|
||||||
|
|
||||||
if let Some(on_option_hovered) =
|
if let Some(on_option_hovered) =
|
||||||
&mut self.on_option_hovered
|
&mut self.on_option_hovered
|
||||||
{
|
&& let Some(option) =
|
||||||
if let Some(option) =
|
|
||||||
menu.hovered_option.and_then(|index| {
|
menu.hovered_option.and_then(|index| {
|
||||||
state
|
state
|
||||||
.filtered_options
|
.filtered_options
|
||||||
.options
|
.options
|
||||||
.get(index)
|
.get(index)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
// Notify the selection
|
// Notify the selection
|
||||||
shell.publish((on_option_hovered)(
|
shell.publish((on_option_hovered)(
|
||||||
option.clone(),
|
option.clone(),
|
||||||
));
|
));
|
||||||
published_message_to_shell = true;
|
published_message_to_shell = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
|
@ -701,21 +697,19 @@ where
|
||||||
|
|
||||||
if let Some(on_option_hovered) =
|
if let Some(on_option_hovered) =
|
||||||
&mut self.on_option_hovered
|
&mut self.on_option_hovered
|
||||||
{
|
&& let Some(option) =
|
||||||
if let Some(option) =
|
|
||||||
menu.hovered_option.and_then(|index| {
|
menu.hovered_option.and_then(|index| {
|
||||||
state
|
state
|
||||||
.filtered_options
|
.filtered_options
|
||||||
.options
|
.options
|
||||||
.get(index)
|
.get(index)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
// Notify the selection
|
// Notify the selection
|
||||||
shell.publish((on_option_hovered)(
|
shell.publish((on_option_hovered)(
|
||||||
option.clone(),
|
option.clone(),
|
||||||
));
|
));
|
||||||
published_message_to_shell = true;
|
published_message_to_shell = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
|
|
||||||
|
|
@ -714,7 +714,7 @@ pub fn bordered_box(theme: &Theme) -> Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
radius: 5.0.into(),
|
radius: 5.0.into(),
|
||||||
color: palette.background.strong.color,
|
color: palette.background.weak.color,
|
||||||
},
|
},
|
||||||
..Style::default()
|
..Style::default()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,13 @@ use crate::text_input::{self, TextInput};
|
||||||
use crate::toggler::{self, Toggler};
|
use crate::toggler::{self, Toggler};
|
||||||
use crate::tooltip::{self, Tooltip};
|
use crate::tooltip::{self, Tooltip};
|
||||||
use crate::vertical_slider::{self, VerticalSlider};
|
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::borrow::Borrow;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
pub use crate::table::table;
|
||||||
|
|
||||||
/// Creates a [`Column`] with the given children.
|
/// Creates a [`Column`] with the given children.
|
||||||
///
|
///
|
||||||
/// Columns distribute their children vertically.
|
/// 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!
|
/// 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>>,
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Pop<'a, (), Message, Theme, Renderer>
|
) -> Sensor<'a, (), Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: core::Renderer,
|
Renderer: core::Renderer,
|
||||||
{
|
{
|
||||||
Pop::new(content)
|
Sensor::new(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Scrollable`] with the provided content.
|
/// Creates a new [`Scrollable`] with the provided content.
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,14 @@ pub mod keyed;
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
pub mod pane_grid;
|
pub mod pane_grid;
|
||||||
pub mod pick_list;
|
pub mod pick_list;
|
||||||
pub mod pop;
|
|
||||||
pub mod progress_bar;
|
pub mod progress_bar;
|
||||||
pub mod radio;
|
pub mod radio;
|
||||||
pub mod row;
|
pub mod row;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
pub mod scrollable;
|
pub mod scrollable;
|
||||||
|
pub mod sensor;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
|
pub mod table;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod text_editor;
|
pub mod text_editor;
|
||||||
pub mod text_input;
|
pub mod text_input;
|
||||||
|
|
@ -73,8 +74,6 @@ pub use pick_list::PickList;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use pin::Pin;
|
pub use pin::Pin;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use pop::Pop;
|
|
||||||
#[doc(no_inline)]
|
|
||||||
pub use progress_bar::ProgressBar;
|
pub use progress_bar::ProgressBar;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use radio::Radio;
|
pub use radio::Radio;
|
||||||
|
|
@ -85,6 +84,8 @@ pub use rule::Rule;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use scrollable::Scrollable;
|
pub use scrollable::Scrollable;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
|
pub use sensor::Sensor;
|
||||||
|
#[doc(no_inline)]
|
||||||
pub use slider::Slider;
|
pub use slider::Slider;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use space::Space;
|
pub use space::Space;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
use crate::core::alignment;
|
||||||
use crate::core::border;
|
use crate::core::border;
|
||||||
use crate::core::font::{self, Font};
|
use crate::core::font::{self, Font};
|
||||||
use crate::core::padding;
|
use crate::core::padding;
|
||||||
|
|
@ -107,13 +108,17 @@ impl Content {
|
||||||
let mut leftover = std::mem::take(&mut self.state.leftover);
|
let mut leftover = std::mem::take(&mut self.state.leftover);
|
||||||
leftover.push_str(markdown);
|
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
|
// Pop the last item
|
||||||
let _ = self.items.pop();
|
let _ = self.items.pop();
|
||||||
|
|
||||||
// Re-parse last item and new text
|
// Re-parse last item and new text
|
||||||
for (item, source, broken_links) in
|
for (item, source, broken_links) in parse_with(&mut self.state, input) {
|
||||||
parse_with(&mut self.state, &leftover)
|
|
||||||
{
|
|
||||||
if !broken_links.is_empty() {
|
if !broken_links.is_empty() {
|
||||||
let _ = self.incomplete.insert(
|
let _ = self.incomplete.insert(
|
||||||
self.items.len(),
|
self.items.len(),
|
||||||
|
|
@ -127,6 +132,8 @@ impl Content {
|
||||||
self.items.push(item);
|
self.items.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.state.leftover.push_str(&leftover[input.len()..]);
|
||||||
|
|
||||||
// Re-parse incomplete sections if new references are available
|
// Re-parse incomplete sections if new references are available
|
||||||
if !self.incomplete.is_empty() {
|
if !self.incomplete.is_empty() {
|
||||||
self.incomplete.retain(|index, section| {
|
self.incomplete.retain(|index, section| {
|
||||||
|
|
@ -215,6 +222,29 @@ pub enum Item {
|
||||||
Quote(Vec<Item>),
|
Quote(Vec<Item>),
|
||||||
/// A horizontal separator.
|
/// A horizontal separator.
|
||||||
Rule,
|
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.
|
/// A bunch of parsed Markdown text.
|
||||||
|
|
@ -462,6 +492,12 @@ fn parse_with<'a>(
|
||||||
enum Scope {
|
enum Scope {
|
||||||
List(List),
|
List(List),
|
||||||
Quote(Vec<Item>),
|
Quote(Vec<Item>),
|
||||||
|
Table {
|
||||||
|
alignment: Vec<pulldown_cmark::Alignment>,
|
||||||
|
columns: Vec<Column>,
|
||||||
|
rows: Vec<Row>,
|
||||||
|
current: Vec<Item>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
struct List {
|
struct List {
|
||||||
|
|
@ -479,7 +515,6 @@ fn parse_with<'a>(
|
||||||
let mut emphasis = false;
|
let mut emphasis = false;
|
||||||
let mut strikethrough = false;
|
let mut strikethrough = false;
|
||||||
let mut metadata = false;
|
let mut metadata = false;
|
||||||
let mut table = false;
|
|
||||||
let mut code_block = false;
|
let mut code_block = false;
|
||||||
let mut link = None;
|
let mut link = None;
|
||||||
let mut image = None;
|
let mut image = None;
|
||||||
|
|
@ -535,6 +570,9 @@ fn parse_with<'a>(
|
||||||
Scope::Quote(items) => {
|
Scope::Quote(items) => {
|
||||||
items.push(item);
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
Scope::Table { current, .. } => {
|
||||||
|
current.push(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
@ -555,21 +593,19 @@ fn parse_with<'a>(
|
||||||
#[allow(clippy::drain_collect)]
|
#[allow(clippy::drain_collect)]
|
||||||
parser.filter_map(move |(event, source)| match event {
|
parser.filter_map(move |(event, source)| match event {
|
||||||
pulldown_cmark::Event::Start(tag) => match tag {
|
pulldown_cmark::Event::Start(tag) => match tag {
|
||||||
pulldown_cmark::Tag::Strong if !metadata && !table => {
|
pulldown_cmark::Tag::Strong if !metadata => {
|
||||||
strong = true;
|
strong = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::Emphasis if !metadata && !table => {
|
pulldown_cmark::Tag::Emphasis if !metadata => {
|
||||||
emphasis = true;
|
emphasis = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::Strikethrough if !metadata && !table => {
|
pulldown_cmark::Tag::Strikethrough if !metadata => {
|
||||||
strikethrough = true;
|
strikethrough = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::Link { dest_url, .. }
|
pulldown_cmark::Tag::Link { dest_url, .. } if !metadata => {
|
||||||
if !metadata && !table =>
|
|
||||||
{
|
|
||||||
match Url::parse(&dest_url) {
|
match Url::parse(&dest_url) {
|
||||||
Ok(url)
|
Ok(url)
|
||||||
if url.scheme() == "http"
|
if url.scheme() == "http"
|
||||||
|
|
@ -584,13 +620,13 @@ fn parse_with<'a>(
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::Image {
|
pulldown_cmark::Tag::Image {
|
||||||
dest_url, title, ..
|
dest_url, title, ..
|
||||||
} if !metadata && !table => {
|
} if !metadata => {
|
||||||
image = Url::parse(&dest_url)
|
image = Url::parse(&dest_url)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|url| (url, title.into_string()));
|
.map(|url| (url, title.into_string()));
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::List(first_item) if !metadata && !table => {
|
pulldown_cmark::Tag::List(first_item) if !metadata => {
|
||||||
let prev = if spans.is_empty() {
|
let prev = if spans.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -616,7 +652,7 @@ fn parse_with<'a>(
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::BlockQuote(_kind) if !metadata && !table => {
|
pulldown_cmark::Tag::BlockQuote(_kind) if !metadata => {
|
||||||
let prev = if spans.is_empty() {
|
let prev = if spans.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -634,7 +670,7 @@ fn parse_with<'a>(
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::CodeBlock(
|
pulldown_cmark::Tag::CodeBlock(
|
||||||
pulldown_cmark::CodeBlockKind::Fenced(language),
|
pulldown_cmark::CodeBlockKind::Fenced(language),
|
||||||
) if !metadata && !table => {
|
) if !metadata => {
|
||||||
#[cfg(feature = "highlighter")]
|
#[cfg(feature = "highlighter")]
|
||||||
{
|
{
|
||||||
highlighter = Some({
|
highlighter = Some({
|
||||||
|
|
@ -672,38 +708,54 @@ fn parse_with<'a>(
|
||||||
metadata = true;
|
metadata = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Tag::Table(_) => {
|
pulldown_cmark::Tag::Table(alignment) => {
|
||||||
table = true;
|
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
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
pulldown_cmark::Event::End(tag) => match tag {
|
pulldown_cmark::Event::End(tag) => match tag {
|
||||||
pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => {
|
pulldown_cmark::TagEnd::Heading(level) if !metadata => produce(
|
||||||
produce(
|
state.borrow_mut(),
|
||||||
state.borrow_mut(),
|
&mut stack,
|
||||||
&mut stack,
|
Item::Heading(level, Text::new(spans.drain(..).collect())),
|
||||||
Item::Heading(level, Text::new(spans.drain(..).collect())),
|
source,
|
||||||
source,
|
),
|
||||||
)
|
pulldown_cmark::TagEnd::Strong if !metadata => {
|
||||||
}
|
|
||||||
pulldown_cmark::TagEnd::Strong if !metadata && !table => {
|
|
||||||
strong = false;
|
strong = false;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Emphasis if !metadata && !table => {
|
pulldown_cmark::TagEnd::Emphasis if !metadata => {
|
||||||
emphasis = false;
|
emphasis = false;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Strikethrough if !metadata && !table => {
|
pulldown_cmark::TagEnd::Strikethrough if !metadata => {
|
||||||
strikethrough = false;
|
strikethrough = false;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Link if !metadata && !table => {
|
pulldown_cmark::TagEnd::Link if !metadata => {
|
||||||
link = None;
|
link = None;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Paragraph if !metadata && !table => {
|
pulldown_cmark::TagEnd::Paragraph if !metadata => {
|
||||||
if spans.is_empty() {
|
if spans.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} 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() {
|
if spans.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} 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 = stack.pop()?;
|
||||||
|
|
||||||
let Scope::List(list) = scope else {
|
let Scope::List(list) = scope else {
|
||||||
|
|
@ -744,9 +796,7 @@ fn parse_with<'a>(
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::BlockQuote(_kind)
|
pulldown_cmark::TagEnd::BlockQuote(_kind) if !metadata => {
|
||||||
if !metadata && !table =>
|
|
||||||
{
|
|
||||||
let scope = stack.pop()?;
|
let scope = stack.pop()?;
|
||||||
|
|
||||||
let Scope::Quote(quote) = scope else {
|
let Scope::Quote(quote) = scope else {
|
||||||
|
|
@ -760,7 +810,7 @@ fn parse_with<'a>(
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Image if !metadata && !table => {
|
pulldown_cmark::TagEnd::Image if !metadata => {
|
||||||
let (url, title) = image.take()?;
|
let (url, title) = image.take()?;
|
||||||
let alt = Text::new(spans.drain(..).collect());
|
let alt = Text::new(spans.drain(..).collect());
|
||||||
|
|
||||||
|
|
@ -774,7 +824,7 @@ fn parse_with<'a>(
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => {
|
pulldown_cmark::TagEnd::CodeBlock if !metadata => {
|
||||||
code_block = false;
|
code_block = false;
|
||||||
|
|
||||||
#[cfg(feature = "highlighter")]
|
#[cfg(feature = "highlighter")]
|
||||||
|
|
@ -798,12 +848,60 @@ fn parse_with<'a>(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Table => {
|
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
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
pulldown_cmark::Event::Text(text) if !metadata && !table => {
|
pulldown_cmark::Event::Text(text) if !metadata => {
|
||||||
if code_block {
|
if code_block {
|
||||||
code.push_str(&text);
|
code.push_str(&text);
|
||||||
|
|
||||||
|
|
@ -844,7 +942,7 @@ fn parse_with<'a>(
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Event::Code(code) if !metadata && !table => {
|
pulldown_cmark::Event::Code(code) if !metadata => {
|
||||||
let span = Span::Standard {
|
let span = Span::Standard {
|
||||||
text: code.into_string(),
|
text: code.into_string(),
|
||||||
strong,
|
strong,
|
||||||
|
|
@ -857,7 +955,7 @@ fn parse_with<'a>(
|
||||||
spans.push(span);
|
spans.push(span);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Event::SoftBreak if !metadata && !table => {
|
pulldown_cmark::Event::SoftBreak if !metadata => {
|
||||||
spans.push(Span::Standard {
|
spans.push(Span::Standard {
|
||||||
text: String::from(" "),
|
text: String::from(" "),
|
||||||
strikethrough,
|
strikethrough,
|
||||||
|
|
@ -868,7 +966,7 @@ fn parse_with<'a>(
|
||||||
});
|
});
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Event::HardBreak if !metadata && !table => {
|
pulldown_cmark::Event::HardBreak if !metadata => {
|
||||||
spans.push(Span::Standard {
|
spans.push(Span::Standard {
|
||||||
text: String::from("\n"),
|
text: String::from("\n"),
|
||||||
strikethrough,
|
strikethrough,
|
||||||
|
|
@ -974,8 +1072,8 @@ impl Style {
|
||||||
Self {
|
Self {
|
||||||
inline_code_padding: padding::left(1).right(1),
|
inline_code_padding: padding::left(1).right(1),
|
||||||
inline_code_highlight: Highlight {
|
inline_code_highlight: Highlight {
|
||||||
background: color!(0x111).into(),
|
background: color!(0x111111).into(),
|
||||||
border: border::rounded(2),
|
border: border::rounded(4),
|
||||||
},
|
},
|
||||||
inline_code_color: Color::WHITE,
|
inline_code_color: Color::WHITE,
|
||||||
link_color: palette.primary,
|
link_color: palette.primary,
|
||||||
|
|
@ -1113,6 +1211,7 @@ where
|
||||||
} => viewer.ordered_list(settings, *start, items),
|
} => viewer.ordered_list(settings, *start, items),
|
||||||
Item::Quote(quote) => viewer.quote(settings, quote),
|
Item::Quote(quote) => viewer.quote(settings, quote),
|
||||||
Item::Rule => viewer.rule(settings),
|
Item::Rule => viewer.rule(settings),
|
||||||
|
Item::Table { columns, rows } => viewer.table(settings, columns, rows),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1222,9 +1321,14 @@ where
|
||||||
Theme: Catalog + 'a,
|
Theme: Catalog + 'a,
|
||||||
Renderer: core::text::Renderer<Font = Font> + '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)| {
|
column(items.iter().enumerate().map(|(i, items)| {
|
||||||
row![
|
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(
|
view_with(
|
||||||
items,
|
items,
|
||||||
Settings {
|
Settings {
|
||||||
|
|
@ -1238,7 +1342,6 @@ where
|
||||||
.into()
|
.into()
|
||||||
}))
|
}))
|
||||||
.spacing(settings.spacing * 0.75)
|
.spacing(settings.spacing * 0.75)
|
||||||
.padding([0.0, settings.spacing.0])
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1313,6 +1416,80 @@ where
|
||||||
horizontal_rule(2).into()
|
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`].
|
/// A view strategy to display a Markdown [`Item`].
|
||||||
pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
|
pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
|
||||||
where
|
where
|
||||||
|
|
@ -1429,6 +1606,18 @@ where
|
||||||
) -> Element<'a, Message, Theme, Renderer> {
|
) -> Element<'a, Message, Theme, Renderer> {
|
||||||
rule()
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -1446,7 +1635,11 @@ where
|
||||||
|
|
||||||
/// The theme catalog of Markdown items.
|
/// The theme catalog of Markdown items.
|
||||||
pub trait Catalog:
|
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.
|
/// The styling class of a Markdown code block.
|
||||||
fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;
|
fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;
|
||||||
|
|
|
||||||
|
|
@ -377,24 +377,24 @@ fn update<Message: Clone, Theme, Renderer>(
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(position) = cursor_position {
|
if let Some(position) = cursor_position
|
||||||
if let Some(message) = widget.on_double_click.as_ref() {
|
&& let Some(message) = widget.on_double_click.as_ref()
|
||||||
let new_click = mouse::Click::new(
|
{
|
||||||
position,
|
let new_click = mouse::Click::new(
|
||||||
mouse::Button::Left,
|
position,
|
||||||
state.previous_click,
|
mouse::Button::Left,
|
||||||
);
|
state.previous_click,
|
||||||
|
);
|
||||||
|
|
||||||
if new_click.kind() == mouse::click::Kind::Double {
|
if new_click.kind() == mouse::click::Kind::Double {
|
||||||
shell.publish(message.clone());
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
|
|
||||||
|
|
@ -407,13 +407,12 @@ where
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds())
|
||||||
if let Some(index) = *self.hovered_option {
|
&& let Some(index) = *self.hovered_option
|
||||||
if let Some(option) = self.options.get(index) {
|
&& let Some(option) = self.options.get(index)
|
||||||
shell.publish((self.on_selected)(option.clone()));
|
{
|
||||||
shell.capture_event();
|
shell.publish((self.on_selected)(option.clone()));
|
||||||
}
|
shell.capture_event();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||||
|
|
@ -431,19 +430,16 @@ where
|
||||||
let new_hovered_option =
|
let new_hovered_option =
|
||||||
(cursor_position.y / option_height) as usize;
|
(cursor_position.y / option_height) as usize;
|
||||||
|
|
||||||
if *self.hovered_option != Some(new_hovered_option) {
|
if *self.hovered_option != Some(new_hovered_option)
|
||||||
if let Some(option) =
|
&& let Some(option) =
|
||||||
self.options.get(new_hovered_option)
|
self.options.get(new_hovered_option)
|
||||||
|
{
|
||||||
|
if let Some(on_option_hovered) = self.on_option_hovered
|
||||||
{
|
{
|
||||||
if let Some(on_option_hovered) =
|
shell.publish(on_option_hovered(option.clone()));
|
||||||
self.on_option_hovered
|
|
||||||
{
|
|
||||||
shell
|
|
||||||
.publish(on_option_hovered(option.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
shell.request_redraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.hovered_option = Some(new_hovered_option);
|
*self.hovered_option = Some(new_hovered_option);
|
||||||
|
|
@ -464,11 +460,11 @@ where
|
||||||
*self.hovered_option =
|
*self.hovered_option =
|
||||||
Some((cursor_position.y / option_height) as usize);
|
Some((cursor_position.y / option_height) as usize);
|
||||||
|
|
||||||
if let Some(index) = *self.hovered_option {
|
if let Some(index) = *self.hovered_option
|
||||||
if let Some(option) = self.options.get(index) {
|
&& let Some(option) = self.options.get(index)
|
||||||
shell.publish((self.on_selected)(option.clone()));
|
{
|
||||||
shell.capture_event();
|
shell.publish((self.on_selected)(option.clone()));
|
||||||
}
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -593,56 +593,47 @@ where
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. })
|
| Event::Touch(touch::Event::FingerLifted { .. })
|
||||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||||
if let Some((pane, origin)) = action.picked_pane() {
|
if let Some((pane, origin)) = action.picked_pane()
|
||||||
if let Some(on_drag) = on_drag {
|
&& let Some(on_drag) = on_drag
|
||||||
if let Some(cursor_position) = cursor.position() {
|
&& let Some(cursor_position) = cursor.position()
|
||||||
if cursor_position.distance(origin)
|
{
|
||||||
> DRAG_DEADBAND_DISTANCE
|
if cursor_position.distance(origin) > DRAG_DEADBAND_DISTANCE
|
||||||
{
|
{
|
||||||
let event = if let Some(edge) =
|
let event = if let Some(edge) =
|
||||||
in_edge(layout, cursor_position)
|
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 {
|
DragEvent::Dropped {
|
||||||
pane,
|
pane,
|
||||||
target: Target::Edge(edge),
|
target: Target::Pane(target, region),
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
let dropped_region = self
|
_ => DragEvent::Canceled { pane },
|
||||||
.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,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
shell.publish(on_drag(event));
|
||||||
|
} else {
|
||||||
|
shell.publish(on_drag(DragEvent::Canceled { pane }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -660,34 +651,33 @@ where
|
||||||
bounds.size(),
|
bounds.size(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((axis, rectangle, _)) = splits.get(&split) {
|
if let Some((axis, rectangle, _)) = splits.get(&split)
|
||||||
if let Some(cursor_position) = cursor.position() {
|
&& let Some(cursor_position) = cursor.position()
|
||||||
let ratio = match axis {
|
{
|
||||||
Axis::Horizontal => {
|
let ratio = match axis {
|
||||||
let position = cursor_position.y
|
Axis::Horizontal => {
|
||||||
- bounds.y
|
let position = cursor_position.y
|
||||||
- rectangle.y;
|
- bounds.y
|
||||||
|
- rectangle.y;
|
||||||
|
|
||||||
(position / rectangle.height)
|
(position / rectangle.height)
|
||||||
.clamp(0.0, 1.0)
|
.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
Axis::Vertical => {
|
Axis::Vertical => {
|
||||||
let position = cursor_position.x
|
let position = cursor_position.x
|
||||||
- bounds.x
|
- bounds.x
|
||||||
- rectangle.x;
|
- rectangle.x;
|
||||||
|
|
||||||
(position / rectangle.width)
|
(position / rectangle.width).clamp(0.0, 1.0)
|
||||||
.clamp(0.0, 1.0)
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
shell.publish(on_resize(ResizeEvent {
|
shell.publish(on_resize(ResizeEvent {
|
||||||
split,
|
split,
|
||||||
ratio,
|
ratio,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if action.picked_pane().is_some() {
|
} else if action.picked_pane().is_some() {
|
||||||
shell.request_redraw();
|
shell.request_redraw();
|
||||||
|
|
@ -889,24 +879,23 @@ where
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
if picked_pane.is_some() && pane_in_edge.is_none() {
|
if picked_pane.is_some()
|
||||||
if let Some(region) =
|
&& pane_in_edge.is_none()
|
||||||
|
&& let Some(region) =
|
||||||
cursor.position().and_then(|cursor_position| {
|
cursor.position().and_then(|cursor_position| {
|
||||||
layout_region(pane_layout, cursor_position)
|
layout_region(pane_layout, cursor_position)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let bounds =
|
let bounds = layout_region_bounds(pane_layout, region);
|
||||||
layout_region_bounds(pane_layout, region);
|
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
border: style.hovered_region.border,
|
border: style.hovered_region.border,
|
||||||
..renderer::Quad::default()
|
..renderer::Quad::default()
|
||||||
},
|
},
|
||||||
style.hovered_region.background,
|
style.hovered_region.background,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -937,64 +926,62 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render picked pane last
|
// Render picked pane last
|
||||||
if let Some(((content, tree), origin, layout)) = render_picked_pane {
|
if let Some(((content, tree), origin, layout)) = render_picked_pane
|
||||||
if let Some(cursor_position) = cursor.position() {
|
&& let Some(cursor_position) = cursor.position()
|
||||||
let bounds = layout.bounds();
|
{
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let translation =
|
let translation = cursor_position - Point::new(origin.x, origin.y);
|
||||||
cursor_position - Point::new(origin.x, origin.y);
|
|
||||||
|
|
||||||
renderer.with_translation(translation, |renderer| {
|
renderer.with_translation(translation, |renderer| {
|
||||||
renderer.with_layer(bounds, |renderer| {
|
renderer.with_layer(bounds, |renderer| {
|
||||||
content.draw(
|
content.draw(
|
||||||
tree,
|
tree,
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
defaults,
|
defaults,
|
||||||
layout,
|
layout,
|
||||||
pane_cursor,
|
pane_cursor,
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if picked_pane.is_none() {
|
if picked_pane.is_none()
|
||||||
if let Some((axis, split_region, is_picked)) = picked_split {
|
&& let Some((axis, split_region, is_picked)) = picked_split
|
||||||
let highlight = if is_picked {
|
{
|
||||||
style.picked_split
|
let highlight = if is_picked {
|
||||||
} else {
|
style.picked_split
|
||||||
style.hovered_split
|
} else {
|
||||||
};
|
style.hovered_split
|
||||||
|
};
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: match axis {
|
bounds: match axis {
|
||||||
Axis::Horizontal => Rectangle {
|
Axis::Horizontal => Rectangle {
|
||||||
x: split_region.x,
|
x: split_region.x,
|
||||||
y: (split_region.y
|
y: (split_region.y
|
||||||
+ (split_region.height - highlight.width)
|
+ (split_region.height - highlight.width)
|
||||||
/ 2.0)
|
/ 2.0)
|
||||||
.round(),
|
.round(),
|
||||||
width: split_region.width,
|
width: split_region.width,
|
||||||
height: highlight.width,
|
height: highlight.width,
|
||||||
},
|
},
|
||||||
Axis::Vertical => Rectangle {
|
Axis::Vertical => Rectangle {
|
||||||
x: (split_region.x
|
x: (split_region.x
|
||||||
+ (split_region.width - highlight.width)
|
+ (split_region.width - highlight.width) / 2.0)
|
||||||
/ 2.0)
|
.round(),
|
||||||
.round(),
|
y: split_region.y,
|
||||||
y: split_region.y,
|
width: highlight.width,
|
||||||
width: highlight.width,
|
height: split_region.height,
|
||||||
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));
|
shell.publish(on_click(pane));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(on_drag) = &on_drag {
|
if let Some(on_drag) = &on_drag
|
||||||
if content.can_be_dragged_at(layout, cursor_position) {
|
&& content.can_be_dragged_at(layout, cursor_position)
|
||||||
*action = state::Action::Dragging {
|
{
|
||||||
pane,
|
*action = state::Action::Dragging {
|
||||||
origin: cursor_position,
|
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,
|
pane: Pane,
|
||||||
swap: bool,
|
swap: bool,
|
||||||
) {
|
) {
|
||||||
if let Some((state, _)) = self.close(pane) {
|
if let Some((state, _)) = self.close(pane)
|
||||||
if let Some((new_pane, _)) = self.split(axis, target, state) {
|
&& 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);
|
// Ensure new node corresponds to original closed `Pane` for state continuity
|
||||||
|
self.relabel(new_pane, pane);
|
||||||
|
|
||||||
if swap {
|
if swap {
|
||||||
self.swap(target, pane);
|
self.swap(target, pane);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -257,13 +257,12 @@ impl<T> State<T> {
|
||||||
pane: Pane,
|
pane: Pane,
|
||||||
inverse: bool,
|
inverse: bool,
|
||||||
) {
|
) {
|
||||||
if let Some((state, _)) = self.close(pane) {
|
if let Some((state, _)) = self.close(pane)
|
||||||
if let Some((new_pane, _)) =
|
&& let Some((new_pane, _)) =
|
||||||
self.split_node(axis, None, state, inverse)
|
self.split_node(axis, None, state, inverse)
|
||||||
{
|
{
|
||||||
// Ensure new node corresponds to original closed `Pane` for state continuity
|
// Ensure new node corresponds to original closed `Pane` for state continuity
|
||||||
self.relabel(new_pane, pane);
|
self.relabel(new_pane, pane);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,38 +174,28 @@ where
|
||||||
let title_layout = children.next().unwrap();
|
let title_layout = children.next().unwrap();
|
||||||
let mut show_title = true;
|
let mut show_title = true;
|
||||||
|
|
||||||
if let Some(controls) = &self.controls {
|
if let Some(controls) = &self.controls
|
||||||
if show_controls || self.always_show_controls {
|
&& (show_controls || self.always_show_controls)
|
||||||
let controls_layout = children.next().unwrap();
|
{
|
||||||
if title_layout.bounds().width + controls_layout.bounds().width
|
let controls_layout = children.next().unwrap();
|
||||||
> padded.bounds().width
|
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(compact) = controls.compact.as_ref() {
|
||||||
|
let compact_layout = children.next().unwrap();
|
||||||
|
|
||||||
compact.as_widget().draw(
|
compact.as_widget().draw(
|
||||||
&tree.children[2],
|
&tree.children[2],
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
&inherited_style,
|
&inherited_style,
|
||||||
compact_layout,
|
compact_layout,
|
||||||
cursor,
|
cursor,
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
show_title = false;
|
|
||||||
|
|
||||||
controls.full.as_widget().draw(
|
|
||||||
&tree.children[1],
|
|
||||||
renderer,
|
|
||||||
theme,
|
|
||||||
&inherited_style,
|
|
||||||
controls_layout,
|
|
||||||
cursor,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
show_title = false;
|
||||||
|
|
||||||
controls.full.as_widget().draw(
|
controls.full.as_widget().draw(
|
||||||
&tree.children[1],
|
&tree.children[1],
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -216,6 +206,16 @@ where
|
||||||
viewport,
|
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 {
|
let active = Style {
|
||||||
text_color: palette.background.weak.text,
|
text_color: palette.background.weak.text,
|
||||||
background: palette.background.weak.color.into(),
|
background: palette.background.weak.color.into(),
|
||||||
placeholder_color: palette.background.strong.color,
|
placeholder_color: palette.secondary.base.color,
|
||||||
handle_color: palette.background.weak.text,
|
handle_color: palette.background.weak.text,
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
|
|
|
||||||
|
|
@ -136,23 +136,13 @@ where
|
||||||
let child = child.into();
|
let child = child.into();
|
||||||
let child_size = child.as_widget().size_hint();
|
let child_size = child.as_widget().size_hint();
|
||||||
|
|
||||||
self.width = self.width.enclose(child_size.width);
|
if !child_size.is_void() {
|
||||||
self.height = self.height.enclose(child_size.height);
|
self.width = self.width.enclose(child_size.width);
|
||||||
|
self.height = self.height.enclose(child_size.height);
|
||||||
self.children.push(child);
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extends the [`Row`] with the given children.
|
/// Extends the [`Row`] with the given children.
|
||||||
|
|
@ -170,6 +160,7 @@ where
|
||||||
Wrapping {
|
Wrapping {
|
||||||
row: self,
|
row: self,
|
||||||
vertical_spacing: None,
|
vertical_spacing: None,
|
||||||
|
align_x: alignment::Horizontal::Left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -378,6 +369,7 @@ pub struct Wrapping<
|
||||||
> {
|
> {
|
||||||
row: Row<'a, Message, Theme, Renderer>,
|
row: Row<'a, Message, Theme, Renderer>,
|
||||||
vertical_spacing: Option<f32>,
|
vertical_spacing: Option<f32>,
|
||||||
|
align_x: alignment::Horizontal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Theme, Renderer> Wrapping<'_, Message, Theme, Renderer> {
|
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.vertical_spacing = Some(amount.into().0);
|
||||||
self
|
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>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
|
@ -433,9 +434,9 @@ where
|
||||||
Alignment::End => 1.0,
|
Alignment::End => 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let align = |row_start: std::ops::Range<usize>,
|
let align_y = |row_start: std::ops::Range<usize>,
|
||||||
row_height: f32,
|
row_height: f32,
|
||||||
children: &mut Vec<layout::Node>| {
|
children: &mut Vec<layout::Node>| {
|
||||||
if align_factor != 0.0 {
|
if align_factor != 0.0 {
|
||||||
for node in &mut children[row_start] {
|
for node in &mut children[row_start] {
|
||||||
let height = node.size().height;
|
let height = node.size().height;
|
||||||
|
|
@ -460,7 +461,7 @@ where
|
||||||
if x != 0.0 && x + child_size.width > max_width {
|
if x != 0.0 && x + child_size.width > max_width {
|
||||||
intrinsic_size.width = intrinsic_size.width.max(x - spacing);
|
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;
|
y += row_height + vertical_spacing;
|
||||||
x = 0.0;
|
x = 0.0;
|
||||||
|
|
@ -483,7 +484,42 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
intrinsic_size.height = y + row_height;
|
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 =
|
let size =
|
||||||
limits.resolve(self.row.width, self.row.height, intrinsic_size);
|
limits.resolve(self.row.width, self.row.height, intrinsic_size);
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ where
|
||||||
let style = theme.style(&self.class);
|
let style = theme.style(&self.class);
|
||||||
|
|
||||||
let bounds = if self.is_horizontal {
|
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 (offset, line_width) = style.fill_mode.fill(bounds.width);
|
||||||
let line_x = bounds.x + offset;
|
let line_x = bounds.x + offset;
|
||||||
|
|
@ -146,7 +146,7 @@ where
|
||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
}
|
}
|
||||||
} else {
|
} 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 (offset, line_height) = style.fill_mode.fill(bounds.height);
|
||||||
let line_y = bounds.y + offset;
|
let line_y = bounds.y + offset;
|
||||||
|
|
@ -300,3 +300,15 @@ pub fn default(theme: &Theme) -> Style {
|
||||||
snap: true,
|
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 !had_input_method
|
||||||
if let InputMethod::Enabled { position, .. } =
|
&& let InputMethod::Enabled { position, .. } =
|
||||||
shell.input_method_mut()
|
shell.input_method_mut()
|
||||||
{
|
{
|
||||||
*position = *position - translation;
|
*position = *position - translation;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1091,23 +1090,22 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scroller) = scrollbar.scroller {
|
if let Some(scroller) = scrollbar.scroller
|
||||||
if scroller.bounds.width > 0.0
|
&& scroller.bounds.width > 0.0
|
||||||
&& scroller.bounds.height > 0.0
|
&& scroller.bounds.height > 0.0
|
||||||
&& (style.scroller.color != Color::TRANSPARENT
|
&& (style.scroller.color != Color::TRANSPARENT
|
||||||
|| (style.scroller.border.color
|
|| (style.scroller.border.color
|
||||||
!= Color::TRANSPARENT
|
!= Color::TRANSPARENT
|
||||||
&& style.scroller.border.width > 0.0))
|
&& style.scroller.border.width > 0.0))
|
||||||
{
|
{
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: scroller.bounds,
|
bounds: scroller.bounds,
|
||||||
border: style.scroller.border,
|
border: style.scroller.border,
|
||||||
..renderer::Quad::default()
|
..renderer::Quad::default()
|
||||||
},
|
},
|
||||||
style.scroller.color,
|
style.scroller.color,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2069,7 +2067,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
background: Some(palette.background.weak.color.into()),
|
background: Some(palette.background.weak.color.into()),
|
||||||
border: border::rounded(2),
|
border: border::rounded(2),
|
||||||
scroller: Scroller {
|
scroller: Scroller {
|
||||||
color: palette.background.strong.color,
|
color: palette.background.strongest.color,
|
||||||
border: border::rounded(2),
|
border: border::rounded(2),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::core::{
|
||||||
///
|
///
|
||||||
/// It can even notify you with anticipation at a given distance!
|
/// It can even notify you with anticipation at a given distance!
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Pop<
|
pub struct Sensor<
|
||||||
'a,
|
'a,
|
||||||
Key,
|
Key,
|
||||||
Message,
|
Message,
|
||||||
|
|
@ -32,11 +32,11 @@ pub struct Pop<
|
||||||
delay: Duration,
|
delay: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Pop<'a, (), Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Sensor<'a, (), Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: core::Renderer,
|
Renderer: core::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Pop`] widget with the given content.
|
/// Creates a new [`Sensor`] widget with the given content.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Self {
|
) -> 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
|
where
|
||||||
Key: self::Key,
|
Key: self::Key,
|
||||||
Renderer: core::Renderer,
|
Renderer: core::Renderer,
|
||||||
|
|
@ -82,17 +83,17 @@ where
|
||||||
self
|
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>(
|
pub fn key<K>(
|
||||||
self,
|
self,
|
||||||
key: K,
|
key: K,
|
||||||
) -> Pop<'a, impl self::Key, Message, Theme, Renderer>
|
) -> Sensor<'a, impl self::Key, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
K: Clone + PartialEq + 'static,
|
K: Clone + PartialEq + 'static,
|
||||||
{
|
{
|
||||||
Pop {
|
Sensor {
|
||||||
content: self.content,
|
content: self.content,
|
||||||
key: OwnedKey(key),
|
key: OwnedKey(key),
|
||||||
on_show: self.on_show,
|
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>(
|
pub fn key_ref<K>(
|
||||||
self,
|
self,
|
||||||
key: &'a K,
|
key: &'a K,
|
||||||
) -> Pop<'a, &'a K, Message, Theme, Renderer>
|
) -> Sensor<'a, &'a K, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
K: ToOwned + PartialEq<K::Owned> + ?Sized,
|
K: ToOwned + PartialEq<K::Owned> + ?Sized,
|
||||||
K::Owned: 'static,
|
K::Owned: 'static,
|
||||||
{
|
{
|
||||||
Pop {
|
Sensor {
|
||||||
content: self.content,
|
content: self.content,
|
||||||
key,
|
key,
|
||||||
on_show: self.on_show,
|
on_show: self.on_show,
|
||||||
|
|
@ -158,7 +159,7 @@ struct State<Key> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
impl<Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
for Pop<'_, Key, Message, Theme, Renderer>
|
for Sensor<'_, Key, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Key: self::Key,
|
Key: self::Key,
|
||||||
Renderer: core::Renderer,
|
Renderer: core::Renderer,
|
||||||
|
|
@ -212,7 +213,16 @@ where
|
||||||
|
|
||||||
let distance = top_left_distance.min(bottom_right_distance);
|
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 distance <= self.anticipate.0 {
|
||||||
if let Some(on_resize) = &self.on_resize {
|
if let Some(on_resize) = &self.on_resize {
|
||||||
let size = bounds.size();
|
let size = bounds.size();
|
||||||
|
|
@ -226,7 +236,7 @@ where
|
||||||
state.has_popped_in = false;
|
state.has_popped_in = false;
|
||||||
state.should_notify_at = Some((false, *now + self.delay));
|
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();
|
let size = bounds.size();
|
||||||
|
|
||||||
state.has_popped_in = true;
|
state.has_popped_in = true;
|
||||||
|
|
@ -356,7 +366,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Key, Message, Theme, Renderer>
|
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>
|
for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
|
|
@ -364,7 +374,7 @@ where
|
||||||
Renderer: core::Renderer + 'a,
|
Renderer: core::Renderer + 'a,
|
||||||
Theme: '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)
|
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)) => {
|
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||||
if let Some(focus) = &mut state.focus {
|
if let Some(focus) = &mut state.focus
|
||||||
if focus.is_window_focused {
|
&& focus.is_window_focused
|
||||||
focus.now = *now;
|
{
|
||||||
|
focus.now = *now;
|
||||||
|
|
||||||
let millis_until_redraw =
|
let millis_until_redraw =
|
||||||
Focus::CURSOR_BLINK_INTERVAL_MILLIS
|
Focus::CURSOR_BLINK_INTERVAL_MILLIS
|
||||||
- (focus.now - focus.updated_at).as_millis()
|
- (focus.now - focus.updated_at).as_millis()
|
||||||
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
|
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
|
||||||
|
|
||||||
shell.request_redraw_at(
|
shell.request_redraw_at(
|
||||||
focus.now
|
focus.now
|
||||||
+ Duration::from_millis(
|
+ Duration::from_millis(millis_until_redraw as u64),
|
||||||
millis_until_redraw as u64,
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -1374,8 +1372,6 @@ pub struct Style {
|
||||||
pub background: Background,
|
pub background: Background,
|
||||||
/// The [`Border`] of the text input.
|
/// The [`Border`] of the text input.
|
||||||
pub border: Border,
|
pub border: Border,
|
||||||
/// The [`Color`] of the icon of the text input.
|
|
||||||
pub icon: Color,
|
|
||||||
/// The [`Color`] of the placeholder of the text input.
|
/// The [`Color`] of the placeholder of the text input.
|
||||||
pub placeholder: Color,
|
pub placeholder: Color,
|
||||||
/// The [`Color`] of the value of the text input.
|
/// The [`Color`] of the value of the text input.
|
||||||
|
|
@ -1422,8 +1418,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: palette.background.strong.color,
|
color: palette.background.strong.color,
|
||||||
},
|
},
|
||||||
icon: palette.background.weak.text,
|
placeholder: palette.secondary.base.color,
|
||||||
placeholder: palette.background.strong.color,
|
|
||||||
value: palette.background.base.text,
|
value: palette.background.base.text,
|
||||||
selection: palette.primary.weak.color,
|
selection: palette.primary.weak.color,
|
||||||
};
|
};
|
||||||
|
|
@ -1447,6 +1442,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
Status::Disabled => Style {
|
Status::Disabled => Style {
|
||||||
background: Background::Color(palette.background.weak.color),
|
background: Background::Color(palette.background.weak.color),
|
||||||
value: active.placeholder,
|
value: active.placeholder,
|
||||||
|
placeholder: palette.background.strongest.color,
|
||||||
..active
|
..active
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1247,12 +1247,12 @@ where
|
||||||
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
if state.is_focused.is_some() {
|
if state.is_focused.is_some()
|
||||||
if let keyboard::Key::Character("v") = key.as_ref() {
|
&& let keyboard::Key::Character("v") = key.as_ref()
|
||||||
state.is_pasting = None;
|
{
|
||||||
|
state.is_pasting = None;
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
@ -1328,32 +1328,31 @@ where
|
||||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused
|
||||||
if focus.is_window_focused {
|
&& focus.is_window_focused
|
||||||
if matches!(
|
{
|
||||||
state.cursor.state(&self.value),
|
if matches!(
|
||||||
cursor::State::Index(_)
|
state.cursor.state(&self.value),
|
||||||
) {
|
cursor::State::Index(_)
|
||||||
focus.now = *now;
|
) {
|
||||||
|
focus.now = *now;
|
||||||
|
|
||||||
let millis_until_redraw =
|
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||||
CURSOR_BLINK_INTERVAL_MILLIS
|
- (*now - focus.updated_at).as_millis()
|
||||||
- (*now - focus.updated_at).as_millis()
|
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||||
% CURSOR_BLINK_INTERVAL_MILLIS;
|
|
||||||
|
|
||||||
shell.request_redraw_at(
|
shell.request_redraw_at(
|
||||||
*now + Duration::from_millis(
|
*now + Duration::from_millis(
|
||||||
millis_until_redraw as u64,
|
millis_until_redraw as u64,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
shell.request_input_method(&self.input_method(
|
|
||||||
state,
|
|
||||||
layout,
|
|
||||||
&self.value,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
border: Border {
|
||||||
radius: 2.0.into(),
|
radius: 2.0.into(),
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: palette.background.strongest.color,
|
color: palette.background.strong.color,
|
||||||
},
|
},
|
||||||
icon: palette.background.weak.text,
|
icon: palette.background.weak.text,
|
||||||
placeholder: palette.background.strongest.color,
|
placeholder: palette.secondary.base.color,
|
||||||
value: palette.background.base.text,
|
value: palette.background.base.text,
|
||||||
selection: palette.primary.weak.color,
|
selection: palette.primary.weak.color,
|
||||||
};
|
};
|
||||||
|
|
@ -1844,6 +1843,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
Status::Disabled => Style {
|
Status::Disabled => Style {
|
||||||
background: Background::Color(palette.background.weak.color),
|
background: Background::Color(palette.background.weak.color),
|
||||||
value: active.placeholder,
|
value: active.placeholder,
|
||||||
|
placeholder: palette.background.strongest.color,
|
||||||
..active
|
..active
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -394,9 +394,6 @@ where
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
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
|
/// The space ratio between the background Quad and the Toggler bounds, and
|
||||||
/// between the background Quad and foreground Quad.
|
/// between the background Quad and foreground Quad.
|
||||||
const SPACE_RATIO: f32 = 0.05;
|
const SPACE_RATIO: f32 = 0.05;
|
||||||
|
|
@ -423,7 +420,7 @@ where
|
||||||
let style = theme
|
let style = theme
|
||||||
.style(&self.class, self.last_status.unwrap_or(Status::Disabled));
|
.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 space = (SPACE_RATIO * bounds.height).round();
|
||||||
|
|
||||||
let toggler_background_bounds = Rectangle {
|
let toggler_background_bounds = Rectangle {
|
||||||
|
|
@ -557,7 +554,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
let background = match status {
|
let background = match status {
|
||||||
Status::Active { is_toggled } | Status::Hovered { is_toggled } => {
|
Status::Active { is_toggled } | Status::Hovered { is_toggled } => {
|
||||||
if is_toggled {
|
if is_toggled {
|
||||||
palette.primary.strong.color
|
palette.primary.base.color
|
||||||
} else {
|
} else {
|
||||||
palette.background.strong.color
|
palette.background.strong.color
|
||||||
}
|
}
|
||||||
|
|
@ -568,7 +565,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
let foreground = match status {
|
let foreground = match status {
|
||||||
Status::Active { is_toggled } => {
|
Status::Active { is_toggled } => {
|
||||||
if is_toggled {
|
if is_toggled {
|
||||||
palette.primary.strong.text
|
palette.primary.base.text
|
||||||
} else {
|
} else {
|
||||||
palette.background.base.color
|
palette.background.base.color
|
||||||
}
|
}
|
||||||
|
|
@ -577,13 +574,13 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
if is_toggled {
|
if is_toggled {
|
||||||
Color {
|
Color {
|
||||||
a: 0.5,
|
a: 0.5,
|
||||||
..palette.primary.strong.text
|
..palette.primary.base.text
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
palette.background.weak.color
|
palette.background.weak.color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status::Disabled => palette.background.base.color,
|
Status::Disabled => palette.background.weakest.color,
|
||||||
};
|
};
|
||||||
|
|
||||||
Style {
|
Style {
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ pub fn window_event(
|
||||||
Ime::Enabled => input_method::Event::Opened,
|
Ime::Enabled => input_method::Event::Opened,
|
||||||
Ime::Preedit(content, size) => input_method::Event::Preedit(
|
Ime::Preedit(content, size) => input_method::Event::Preedit(
|
||||||
content,
|
content,
|
||||||
size.map(|(start, end)| (start..end)),
|
size.map(|(start, end)| start..end),
|
||||||
),
|
),
|
||||||
Ime::Commit(content) => input_method::Event::Commit(content),
|
Ime::Commit(content) => input_method::Event::Commit(content),
|
||||||
Ime::Disabled => input_method::Event::Closed,
|
Ime::Disabled => input_method::Event::Closed,
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ use window::WindowManager;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::slice;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Runs a [`Program`] with the provided settings.
|
/// Runs a [`Program`] with the provided settings.
|
||||||
|
|
@ -652,11 +653,11 @@ async fn run_instance<P>(
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
for (_id, window) in window_manager.iter_mut() {
|
for (_id, window) in window_manager.iter_mut() {
|
||||||
if let Some(redraw_at) = window.redraw_at {
|
if let Some(redraw_at) = window.redraw_at
|
||||||
if redraw_at <= now {
|
&& redraw_at <= now
|
||||||
window.raw.request_redraw();
|
{
|
||||||
window.redraw_at = None;
|
window.raw.request_redraw();
|
||||||
}
|
window.redraw_at = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -760,7 +761,7 @@ async fn run_instance<P>(
|
||||||
|
|
||||||
let draw_span = debug::draw(id);
|
let draw_span = debug::draw(id);
|
||||||
let (ui_state, _) = ui.update(
|
let (ui_state, _) = ui.update(
|
||||||
&[redraw_event.clone()],
|
slice::from_ref(&redraw_event),
|
||||||
cursor,
|
cursor,
|
||||||
&mut window.renderer,
|
&mut window.renderer,
|
||||||
&mut clipboard,
|
&mut clipboard,
|
||||||
|
|
@ -1347,17 +1348,14 @@ fn run_action<'a, P, C>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window::Action::ShowSystemMenu(id) => {
|
window::Action::ShowSystemMenu(id) => {
|
||||||
if let Some(window) = window_manager.get_mut(id) {
|
if let Some(window) = window_manager.get_mut(id)
|
||||||
if let mouse::Cursor::Available(point) =
|
&& let mouse::Cursor::Available(point) =
|
||||||
window.state.cursor()
|
window.state.cursor()
|
||||||
{
|
{
|
||||||
window.raw.show_window_menu(
|
window.raw.show_window_menu(winit::dpi::LogicalPosition {
|
||||||
winit::dpi::LogicalPosition {
|
x: point.x,
|
||||||
x: point.x,
|
y: point.y,
|
||||||
y: point.y,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window::Action::GetRawId(id, channel) => {
|
window::Action::GetRawId(id, channel) => {
|
||||||
|
|
@ -1376,20 +1374,20 @@ fn run_action<'a, P, C>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window::Action::Screenshot(id, channel) => {
|
window::Action::Screenshot(id, channel) => {
|
||||||
if let Some(window) = window_manager.get_mut(id) {
|
if let Some(window) = window_manager.get_mut(id)
|
||||||
if let Some(compositor) = compositor {
|
&& let Some(compositor) = compositor
|
||||||
let bytes = compositor.screenshot(
|
{
|
||||||
&mut window.renderer,
|
let bytes = compositor.screenshot(
|
||||||
window.state.viewport(),
|
&mut window.renderer,
|
||||||
window.state.background_color(),
|
window.state.viewport(),
|
||||||
);
|
window.state.background_color(),
|
||||||
|
);
|
||||||
|
|
||||||
let _ = channel.send(core::window::Screenshot::new(
|
let _ = channel.send(core::window::Screenshot::new(
|
||||||
bytes,
|
bytes,
|
||||||
window.state.physical_size(),
|
window.state.physical_size(),
|
||||||
window.state.viewport().scale_factor(),
|
window.state.viewport().scale_factor(),
|
||||||
));
|
));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window::Action::EnableMousePassthrough(id) => {
|
window::Action::EnableMousePassthrough(id) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue