Merge branch 'master' into advanced-text
This commit is contained in:
commit
4bae457c37
73 changed files with 1586 additions and 703 deletions
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -6,6 +6,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.0] - 2023-04-13
|
||||
### Added
|
||||
- `MouseArea` widget. [#1594](https://github.com/iced-rs/iced/pull/1594)
|
||||
- `channel` helper in `subscription`. [#1786](https://github.com/iced-rs/iced/pull/1786)
|
||||
- Configurable `width` for `Scrollable`. [#1749](https://github.com/iced-rs/iced/pull/1749)
|
||||
- Support for disabled `TextInput`. [#1744](https://github.com/iced-rs/iced/pull/1744)
|
||||
- Platform-specific window settings. [#1730](https://github.com/iced-rs/iced/pull/1730)
|
||||
- Left and right colors for sliders. [#1643](https://github.com/iced-rs/iced/pull/1643)
|
||||
- Icon for `TextInput`. [#1702](https://github.com/iced-rs/iced/pull/1702)
|
||||
- Mouse over scrollbar flag for `scrollable::StyleSheet`. [#1669](https://github.com/iced-rs/iced/pull/1669)
|
||||
- Better example for `Radio`. [#1762](https://github.com/iced-rs/iced/pull/1762)
|
||||
|
||||
### Changed
|
||||
- `wgpu` has been updated to `0.15` in `iced_wgpu`. [#1789](https://github.com/iced-rs/iced/pull/1789)
|
||||
- `resvg` has been updated to `0.29` in `iced_graphics`. [#1733](https://github.com/iced-rs/iced/pull/1733)
|
||||
- `subscription::run` now takes a function pointer. [#1723](https://github.com/iced-rs/iced/pull/1723)
|
||||
|
||||
### Fixed
|
||||
- Redundant `on_scroll` messages for `Scrollable`. [#1788](https://github.com/iced-rs/iced/pull/1788)
|
||||
- Outdated items in `ROADMAP.md` [#1782](https://github.com/iced-rs/iced/pull/1782)
|
||||
- Colons in shader labels causing compilation issues in `iced_wgpu`. [#1779](https://github.com/iced-rs/iced/pull/1779)
|
||||
- Re-expose winit features for window servers in Linux. [#1777](https://github.com/iced-rs/iced/pull/1777)
|
||||
- Replacement of application node in Wasm. [#1765](https://github.com/iced-rs/iced/pull/1765)
|
||||
- `clippy` lints for Rust 1.68. [#1755](https://github.com/iced-rs/iced/pull/1755)
|
||||
- Unnecessary `Component` rebuilds. [#1754](https://github.com/iced-rs/iced/pull/1754)
|
||||
- Incorrect package name in checkbox example docs. [#1750](https://github.com/iced-rs/iced/pull/1750)
|
||||
- Fullscreen only working on primary monitor. [#1742](https://github.com/iced-rs/iced/pull/1742)
|
||||
- `Padding::fit` on irregular values for an axis. [#1734](https://github.com/iced-rs/iced/pull/1734)
|
||||
- `Debug` implementation of `Font` displaying its bytes. [#1731](https://github.com/iced-rs/iced/pull/1731)
|
||||
- Sliders bleeding over their rail. [#1721](https://github.com/iced-rs/iced/pull/1721)
|
||||
|
||||
### Removed
|
||||
- `Fill` variant for `Alignment`. [#1735](https://github.com/iced-rs/iced/pull/1735)
|
||||
|
||||
Many thanks to...
|
||||
|
||||
- @ahoneybun
|
||||
- @bq-wrongway
|
||||
- @bungoboingo
|
||||
- @casperstorm
|
||||
- @Davidster
|
||||
- @ElhamAryanpur
|
||||
- @FinnPerry
|
||||
- @GyulyVGC
|
||||
- @JungleTryne
|
||||
- @lupd
|
||||
- @mmstick
|
||||
- @nicksenger
|
||||
- @Night-Hunter-NF
|
||||
- @tarkah
|
||||
- @traxys
|
||||
- @Xaeroxe
|
||||
|
||||
## [0.8.0] - 2023-02-18
|
||||
### Added
|
||||
- Generic pixel units. [#1711](https://github.com/iced-rs/iced/pull/1711)
|
||||
|
|
@ -414,7 +467,8 @@ Many thanks to...
|
|||
### Added
|
||||
- First release! :tada:
|
||||
|
||||
[Unreleased]: https://github.com/iced-rs/iced/compare/0.8.0...HEAD
|
||||
[Unreleased]: https://github.com/iced-rs/iced/compare/0.9.0...HEAD
|
||||
[0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0
|
||||
[0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0
|
||||
[0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0
|
||||
[0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A cross-platform GUI library inspired by Elm"
|
||||
|
|
@ -61,11 +61,11 @@ members = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
iced_core = { version = "0.8", path = "core" }
|
||||
iced_core = { version = "0.9", path = "core" }
|
||||
iced_futures = { version = "0.6", path = "futures" }
|
||||
iced_renderer = { version = "0.1", path = "renderer" }
|
||||
iced_widget = { version = "0.1", path = "widget" }
|
||||
iced_winit = { version = "0.8", path = "winit", features = ["application"] }
|
||||
iced_winit = { version = "0.9", path = "winit", features = ["application"] }
|
||||
thiserror = "1"
|
||||
|
||||
[dependencies.image_rs]
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
|
|||
Add `iced` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced = "0.8"
|
||||
iced = "0.9"
|
||||
```
|
||||
|
||||
If your project is using a Rust edition older than 2021, then you will need to
|
||||
|
|
@ -215,7 +215,7 @@ cargo run --features iced/glow --package game_of_life
|
|||
and then use it in your project with
|
||||
|
||||
```toml
|
||||
iced = { version = "0.8", default-features = false, features = ["glow"] }
|
||||
iced = { version = "0.9", default-features = false, features = ["glow"] }
|
||||
```
|
||||
|
||||
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
|
||||
|
|
|
|||
14
ROADMAP.md
14
ROADMAP.md
|
|
@ -19,6 +19,7 @@ Once a step is completed, it is collapsed and added to this list:
|
|||
* [x] Custom styling ([#146])
|
||||
* [x] Canvas for 2D graphics ([#193])
|
||||
* [x] Basic overlay support ([#444])
|
||||
* [x] Animations [#31]
|
||||
|
||||
[#24]: https://github.com/iced-rs/iced/issues/24
|
||||
[#25]: https://github.com/iced-rs/iced/issues/25
|
||||
|
|
@ -29,6 +30,7 @@ Once a step is completed, it is collapsed and added to this list:
|
|||
[#146]: https://github.com/iced-rs/iced/pull/146
|
||||
[#193]: https://github.com/iced-rs/iced/pull/193
|
||||
[#444]: https://github.com/iced-rs/iced/pull/444
|
||||
[#31]: https://github.com/iced-rs/iced/issues/31
|
||||
|
||||
### Multi-window support ([#27])
|
||||
Open and control multiple windows at runtime.
|
||||
|
|
@ -39,16 +41,7 @@ This approach should also allow us to perform custom optimizations for this part
|
|||
|
||||
[#27]: https://github.com/iced-rs/iced/issues/27
|
||||
|
||||
### Animations ([#31])
|
||||
Allow widgets to request a redraw at a specific time.
|
||||
|
||||
This is a necessary feature to render loading spinners, a blinking text cursor, GIF images, etc.
|
||||
|
||||
[`winit`] allows flexible control of its event loop. We may be able to use [`ControlFlow::WaitUntil`](https://docs.rs/winit/0.20.0-alpha3/winit/event_loop/enum.ControlFlow.html#variant.WaitUntil) for this purpose.
|
||||
|
||||
[#31]: https://github.com/iced-rs/iced/issues/31
|
||||
|
||||
### Canvas widget for 3D graphics ([#32])
|
||||
### Canvas widget for 3D graphics (~~[#32]~~ [#343])
|
||||
A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc.
|
||||
|
||||
As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to.
|
||||
|
|
@ -56,6 +49,7 @@ As a first approach, we could expose the underlying renderer directly here, and
|
|||
In the long run, we could expose a renderer-agnostic abstraction to perform the drawing.
|
||||
|
||||
[#32]: https://github.com/iced-rs/iced/issues/32
|
||||
[#343] https://github.com/iced-rs/iced/issues/343
|
||||
|
||||
### Text shaping and font fallback ([#33])
|
||||
[`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping].
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_core"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "The essential concepts of Iced"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ This crate is meant to be a starting point for an Iced runtime.
|
|||
Add `iced_core` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_core = "0.8"
|
||||
iced_core = "0.9"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! 
|
||||
//!
|
||||
//! [Iced]: https://github.com/iced-rs/iced
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
//! [`iced_web`]: https://github.com/iced-rs/iced_web
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
|
|
|
|||
|
|
@ -12,4 +12,5 @@ pub enum Interaction {
|
|||
Grabbing,
|
||||
ResizingHorizontally,
|
||||
ResizingVertically,
|
||||
NotAllowed,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ use crate::{Clipboard, Length, Point, Rectangle, Shell};
|
|||
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
|
||||
/// `Mesh2D` primitive in [`iced_wgpu`].
|
||||
///
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
|
||||
/// [`lyon`]: https://github.com/nical/lyon
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
|
||||
pub trait Widget<Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
//! Build window-based GUI applications.
|
||||
pub mod icon;
|
||||
|
||||
mod event;
|
||||
mod mode;
|
||||
mod redraw_request;
|
||||
mod user_attention;
|
||||
|
||||
pub use event::Event;
|
||||
pub use icon::Icon;
|
||||
pub use mode::Mode;
|
||||
pub use redraw_request::RedrawRequest;
|
||||
pub use user_attention::UserAttention;
|
||||
|
|
|
|||
80
core/src/window/icon.rs
Normal file
80
core/src/window/icon.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
//! Change the icon of a window.
|
||||
use crate::Size;
|
||||
|
||||
use std::mem;
|
||||
|
||||
/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space.
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Icon, Error> {
|
||||
const PIXEL_SIZE: usize = mem::size_of::<u8>() * 4;
|
||||
|
||||
if rgba.len() % PIXEL_SIZE != 0 {
|
||||
return Err(Error::ByteCountNotDivisibleBy4 {
|
||||
byte_count: rgba.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
||||
|
||||
if pixel_count != (width * height) as usize {
|
||||
return Err(Error::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
width_x_height: (width * height) as usize,
|
||||
pixel_count,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Icon {
|
||||
rgba,
|
||||
size: Size::new(width, height),
|
||||
})
|
||||
}
|
||||
|
||||
/// An window icon normally used for the titlebar or taskbar.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Icon {
|
||||
rgba: Vec<u8>,
|
||||
size: Size<u32>,
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Returns the raw data of the [`Icon`].
|
||||
pub fn into_raw(self) -> (Vec<u8>, Size<u32>) {
|
||||
(self.rgba, self.size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
|
||||
pub enum Error {
|
||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||
/// safely interpreted as 32bpp RGBA pixels.
|
||||
#[error(
|
||||
"The provided RGBA data (with length {byte_count}) isn't divisible \
|
||||
by 4. Therefore, it cannot be safely interpreted as 32bpp RGBA pixels"
|
||||
)]
|
||||
ByteCountNotDivisibleBy4 {
|
||||
/// The length of the provided RGBA data.
|
||||
byte_count: usize,
|
||||
},
|
||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
||||
/// At least one of your arguments is incorrect.
|
||||
#[error(
|
||||
"The number of RGBA pixels ({pixel_count}) does not match the \
|
||||
provided dimensions ({width}x{height})."
|
||||
)]
|
||||
DimensionsVsPixelCount {
|
||||
/// The provided width.
|
||||
width: u32,
|
||||
/// The provided height.
|
||||
height: u32,
|
||||
/// The product of `width` and `height`.
|
||||
width_x_height: usize,
|
||||
/// The amount of pixels of the provided RGBA data.
|
||||
pixel_count: usize,
|
||||
},
|
||||
}
|
||||
52
docs/release_summary.py
Normal file
52
docs/release_summary.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import re
|
||||
import sys
|
||||
import requests
|
||||
from typing import List, Tuple
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python release_summary.py <personal_access_token> <previous_release_branch>")
|
||||
exit(1)
|
||||
|
||||
TOKEN = sys.argv[1]
|
||||
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
|
||||
PR_COMMIT_REGEX = re.compile(r"(?i)Merge pull request #(\d+).*")
|
||||
|
||||
def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]:
|
||||
prs = []
|
||||
compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master"
|
||||
compare_response = requests.get(compare_url, headers=HEADERS)
|
||||
|
||||
if compare_response.status_code == 200:
|
||||
compare_data = compare_response.json()
|
||||
for commit in compare_data["commits"]:
|
||||
match = PR_COMMIT_REGEX.search(commit["commit"]["message"])
|
||||
if match:
|
||||
pr_number = int(match.group(1))
|
||||
pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
||||
pr_response = requests.get(pr_url, headers=HEADERS)
|
||||
if pr_response.status_code == 200:
|
||||
pr_data = pr_response.json()
|
||||
prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"]))
|
||||
else:
|
||||
print(f"Error fetching PR {pr_number}: {pr_response.status_code}")
|
||||
else:
|
||||
print(f"Error comparing branches: {compare_response.status_code}")
|
||||
|
||||
return prs
|
||||
|
||||
def print_pr_list(prs: List[Tuple[str, int, str, str]]):
|
||||
for pr in prs:
|
||||
print(f"- {pr[0]}. [#{pr[1]}]({pr[2]})")
|
||||
|
||||
def print_authors(prs: List[Tuple[str, int, str, str]]):
|
||||
authors = set(pr[3] for pr in prs)
|
||||
print("\nAuthors:")
|
||||
for author in sorted(authors, key=str.casefold):
|
||||
print(f"- @{author}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
repo = "iced-rs/iced"
|
||||
previous_release_branch = sys.argv[2]
|
||||
merged_prs = get_merged_prs_since_release(repo, previous_release_branch)
|
||||
print_pr_list(merged_prs)
|
||||
print_authors(merged_prs)
|
||||
|
|
@ -134,8 +134,8 @@ mod numeric_input {
|
|||
.map(u32::to_string)
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
Event::InputChanged,
|
||||
)
|
||||
.on_input(Event::InputChanged)
|
||||
.padding(10),
|
||||
button("+", Event::IncrementPressed),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ pub struct Download<I> {
|
|||
url: String,
|
||||
}
|
||||
|
||||
async fn download<I: Copy>(
|
||||
id: I,
|
||||
state: State,
|
||||
) -> (Option<(I, Progress)>, State) {
|
||||
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
|
||||
match state {
|
||||
State::Ready(url) => {
|
||||
let response = reqwest::get(&url).await;
|
||||
|
|
@ -30,7 +27,7 @@ async fn download<I: Copy>(
|
|||
Ok(response) => {
|
||||
if let Some(total) = response.content_length() {
|
||||
(
|
||||
Some((id, Progress::Started)),
|
||||
(id, Progress::Started),
|
||||
State::Downloading {
|
||||
response,
|
||||
total,
|
||||
|
|
@ -38,10 +35,10 @@ async fn download<I: Copy>(
|
|||
},
|
||||
)
|
||||
} else {
|
||||
(Some((id, Progress::Errored)), State::Finished)
|
||||
((id, Progress::Errored), State::Finished)
|
||||
}
|
||||
}
|
||||
Err(_) => (Some((id, Progress::Errored)), State::Finished),
|
||||
Err(_) => ((id, Progress::Errored), State::Finished),
|
||||
}
|
||||
}
|
||||
State::Downloading {
|
||||
|
|
@ -55,7 +52,7 @@ async fn download<I: Copy>(
|
|||
let percentage = (downloaded as f32 / total as f32) * 100.0;
|
||||
|
||||
(
|
||||
Some((id, Progress::Advanced(percentage))),
|
||||
(id, Progress::Advanced(percentage)),
|
||||
State::Downloading {
|
||||
response,
|
||||
total,
|
||||
|
|
@ -63,8 +60,8 @@ async fn download<I: Copy>(
|
|||
},
|
||||
)
|
||||
}
|
||||
Ok(None) => (Some((id, Progress::Finished)), State::Finished),
|
||||
Err(_) => (Some((id, Progress::Errored)), State::Finished),
|
||||
Ok(None) => ((id, Progress::Finished), State::Finished),
|
||||
Err(_) => ((id, Progress::Errored), State::Finished),
|
||||
},
|
||||
State::Finished => {
|
||||
// We do not let the stream die, as it would start a
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ iced_winit = { path = "../../winit" }
|
|||
iced_wgpu = { path = "../../wgpu" }
|
||||
iced_widget = { path = "../../widget" }
|
||||
iced_renderer = { path = "../../renderer", features = ["wgpu", "tiny-skia"] }
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.10"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
|
|
|||
|
|
@ -102,11 +102,10 @@ impl Program for Controls {
|
|||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(text_input(
|
||||
"Placeholder",
|
||||
text,
|
||||
Message::TextChanged,
|
||||
)),
|
||||
.push(
|
||||
text_input("Placeholder", text)
|
||||
.on_input(Message::TextChanged),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
let canvas_element = {
|
||||
console_log::init_with_level(log::Level::Debug)?;
|
||||
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
web_sys::window()
|
||||
|
|
@ -49,7 +50,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.build(&event_loop)?;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let window = winit::window::Window::new(&event_loop).unwrap();
|
||||
let window = winit::window::Window::new(&event_loop)?;
|
||||
|
||||
let physical_size = window.inner_size();
|
||||
let mut viewport = Viewport::with_physical_size(
|
||||
|
|
@ -61,7 +62,6 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut clipboard = Clipboard::connect(&window);
|
||||
|
||||
// Initialize wgpu
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let default_backend = wgpu::Backends::GL;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
@ -95,12 +95,16 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let needed_limits = wgpu::Limits::default();
|
||||
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
(
|
||||
surface
|
||||
.get_capabilities(&adapter)
|
||||
capabilities
|
||||
.formats
|
||||
.first()
|
||||
.iter()
|
||||
.filter(|format| format.describe().srgb)
|
||||
.copied()
|
||||
.next()
|
||||
.or_else(|| capabilities.formats.first().copied())
|
||||
.expect("Get preferred format"),
|
||||
adapter
|
||||
.request_device(
|
||||
|
|
|
|||
|
|
@ -213,11 +213,8 @@ impl Sandbox for App {
|
|||
column![
|
||||
scrollable(options).height(Length::Fill),
|
||||
row![
|
||||
text_input(
|
||||
"Add a new option",
|
||||
&self.input,
|
||||
Message::InputChanged,
|
||||
)
|
||||
text_input("Add a new option", &self.input)
|
||||
.on_input(Message::InputChanged)
|
||||
.on_submit(Message::AddItem(self.input.clone())),
|
||||
button(text(format!("Toggle Order ({})", self.order)))
|
||||
.on_press(Message::ToggleOrder)
|
||||
|
|
|
|||
|
|
@ -133,18 +133,16 @@ impl Application for App {
|
|||
column![
|
||||
column![
|
||||
text("Email").size(12),
|
||||
text_input(
|
||||
"abc@123.com",
|
||||
&self.email,
|
||||
Message::Email
|
||||
)
|
||||
text_input("abc@123.com", &self.email,)
|
||||
.on_input(Message::Email)
|
||||
.on_submit(Message::Submit)
|
||||
.padding(5),
|
||||
]
|
||||
.spacing(5),
|
||||
column![
|
||||
text("Password").size(12),
|
||||
text_input("", &self.password, Message::Password)
|
||||
text_input("", &self.password)
|
||||
.on_input(Message::Password)
|
||||
.on_submit(Message::Submit)
|
||||
.password()
|
||||
.padding(5),
|
||||
|
|
|
|||
|
|
@ -49,11 +49,9 @@ impl Sandbox for QRGenerator {
|
|||
.size(70)
|
||||
.style(Color::from([0.5, 0.5, 0.5]));
|
||||
|
||||
let input = text_input(
|
||||
"Type the data of your QR code here...",
|
||||
&self.data,
|
||||
Message::DataChanged,
|
||||
)
|
||||
let input =
|
||||
text_input("Type the data of your QR code here...", &self.data)
|
||||
.on_input(Message::DataChanged)
|
||||
.size(30)
|
||||
.padding(15);
|
||||
|
||||
|
|
|
|||
|
|
@ -338,13 +338,24 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
|
|||
style.active(&theme::Scrollable::Default)
|
||||
}
|
||||
|
||||
fn hovered(&self, style: &Self::Style) -> Scrollbar {
|
||||
style.hovered(&theme::Scrollable::Default)
|
||||
fn hovered(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> Scrollbar {
|
||||
style.hovered(&theme::Scrollable::Default, is_mouse_over_scrollbar)
|
||||
}
|
||||
|
||||
fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
|
||||
fn hovered_horizontal(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> Scrollbar {
|
||||
if is_mouse_over_scrollbar {
|
||||
Scrollbar {
|
||||
background: style.active(&theme::Scrollable::default()).background,
|
||||
background: style
|
||||
.active(&theme::Scrollable::default())
|
||||
.background,
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Default::default(),
|
||||
|
|
@ -355,6 +366,9 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
|
|||
border_color: Default::default(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.active(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,11 +90,8 @@ impl Sandbox for Styling {
|
|||
},
|
||||
);
|
||||
|
||||
let text_input = text_input(
|
||||
"Type something...",
|
||||
&self.input_value,
|
||||
Message::InputChanged,
|
||||
)
|
||||
let text_input = text_input("Type something...", &self.input_value)
|
||||
.on_input(Message::InputChanged)
|
||||
.padding(10)
|
||||
.size(20);
|
||||
|
||||
|
|
|
|||
|
|
@ -119,13 +119,15 @@ impl Application for App {
|
|||
column![
|
||||
subtitle(
|
||||
"Title",
|
||||
text_input("", &self.editing.title, Message::Title)
|
||||
text_input("", &self.editing.title)
|
||||
.on_input(Message::Title)
|
||||
.on_submit(Message::Add)
|
||||
.into()
|
||||
),
|
||||
subtitle(
|
||||
"Message",
|
||||
text_input("", &self.editing.body, Message::Body)
|
||||
text_input("", &self.editing.body)
|
||||
.on_input(Message::Body)
|
||||
.on_submit(Message::Add)
|
||||
.into()
|
||||
),
|
||||
|
|
|
|||
|
|
@ -210,15 +210,12 @@ impl Application for Todos {
|
|||
.style(Color::from([0.5, 0.5, 0.5]))
|
||||
.horizontal_alignment(alignment::Horizontal::Center);
|
||||
|
||||
let input = text_input(
|
||||
"What needs to be done?",
|
||||
input_value,
|
||||
Message::InputChanged,
|
||||
)
|
||||
let input = text_input("What needs to be done?", input_value)
|
||||
.id(INPUT_ID.clone())
|
||||
.on_input(Message::InputChanged)
|
||||
.on_submit(Message::CreateTask)
|
||||
.padding(15)
|
||||
.size(30)
|
||||
.on_submit(Message::CreateTask);
|
||||
.size(30);
|
||||
|
||||
let controls = view_controls(tasks, *filter);
|
||||
let filtered_tasks =
|
||||
|
|
@ -381,12 +378,10 @@ impl Task {
|
|||
.into()
|
||||
}
|
||||
TaskState::Editing => {
|
||||
let text_input = text_input(
|
||||
"Describe your task...",
|
||||
&self.description,
|
||||
TaskMessage::DescriptionEdited,
|
||||
)
|
||||
let text_input =
|
||||
text_input("Describe your task...", &self.description)
|
||||
.id(Self::text_input_id(i))
|
||||
.on_input(TaskMessage::DescriptionEdited)
|
||||
.on_submit(TaskMessage::FinishEdition)
|
||||
.padding(10);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
|||
scrollable, slider, text, text_input, toggler, vertical_space,
|
||||
};
|
||||
use iced::widget::{Button, Column, Container, Slider};
|
||||
use iced::{Color, Element, Length, Renderer, Sandbox, Settings};
|
||||
use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
env_logger::init();
|
||||
|
|
@ -127,6 +127,7 @@ impl Steps {
|
|||
Step::TextInput {
|
||||
value: String::new(),
|
||||
is_secure: false,
|
||||
is_showing_icon: false,
|
||||
},
|
||||
Step::Debugger,
|
||||
Step::End,
|
||||
|
|
@ -171,14 +172,32 @@ impl Steps {
|
|||
|
||||
enum Step {
|
||||
Welcome,
|
||||
Slider { value: u8 },
|
||||
RowsAndColumns { layout: Layout, spacing: u16 },
|
||||
Text { size: u16, color: Color },
|
||||
Radio { selection: Option<Language> },
|
||||
Toggler { can_continue: bool },
|
||||
Image { width: u16 },
|
||||
Slider {
|
||||
value: u8,
|
||||
},
|
||||
RowsAndColumns {
|
||||
layout: Layout,
|
||||
spacing: u16,
|
||||
},
|
||||
Text {
|
||||
size: u16,
|
||||
color: Color,
|
||||
},
|
||||
Radio {
|
||||
selection: Option<Language>,
|
||||
},
|
||||
Toggler {
|
||||
can_continue: bool,
|
||||
},
|
||||
Image {
|
||||
width: u16,
|
||||
},
|
||||
Scrollable,
|
||||
TextInput { value: String, is_secure: bool },
|
||||
TextInput {
|
||||
value: String,
|
||||
is_secure: bool,
|
||||
is_showing_icon: bool,
|
||||
},
|
||||
Debugger,
|
||||
End,
|
||||
}
|
||||
|
|
@ -194,6 +213,7 @@ pub enum StepMessage {
|
|||
ImageWidthChanged(u16),
|
||||
InputChanged(String),
|
||||
ToggleSecureInput(bool),
|
||||
ToggleTextInputIcon(bool),
|
||||
DebugToggled(bool),
|
||||
TogglerChanged(bool),
|
||||
}
|
||||
|
|
@ -256,6 +276,14 @@ impl<'a> Step {
|
|||
*can_continue = value;
|
||||
}
|
||||
}
|
||||
StepMessage::ToggleTextInputIcon(toggle) => {
|
||||
if let Step::TextInput {
|
||||
is_showing_icon, ..
|
||||
} = self
|
||||
{
|
||||
*is_showing_icon = toggle
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -303,9 +331,11 @@ impl<'a> Step {
|
|||
Self::rows_and_columns(*layout, *spacing)
|
||||
}
|
||||
Step::Scrollable => Self::scrollable(),
|
||||
Step::TextInput { value, is_secure } => {
|
||||
Self::text_input(value, *is_secure)
|
||||
}
|
||||
Step::TextInput {
|
||||
value,
|
||||
is_secure,
|
||||
is_showing_icon,
|
||||
} => Self::text_input(value, *is_secure, *is_showing_icon),
|
||||
Step::Debugger => Self::debugger(debug),
|
||||
Step::End => Self::end(),
|
||||
}
|
||||
|
|
@ -530,15 +560,26 @@ impl<'a> Step {
|
|||
)
|
||||
}
|
||||
|
||||
fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> {
|
||||
let text_input = text_input(
|
||||
"Type something to continue...",
|
||||
value,
|
||||
StepMessage::InputChanged,
|
||||
)
|
||||
fn text_input(
|
||||
value: &str,
|
||||
is_secure: bool,
|
||||
is_showing_icon: bool,
|
||||
) -> Column<'a, StepMessage> {
|
||||
let mut text_input = text_input("Type something to continue...", value)
|
||||
.on_input(StepMessage::InputChanged)
|
||||
.padding(10)
|
||||
.size(30);
|
||||
|
||||
if is_showing_icon {
|
||||
text_input = text_input.icon(text_input::Icon {
|
||||
font: Font::default(),
|
||||
code_point: '🚀',
|
||||
size: Some(28.0),
|
||||
spacing: 10.0,
|
||||
side: text_input::Side::Right,
|
||||
});
|
||||
}
|
||||
|
||||
Self::container("Text input")
|
||||
.push("Use a text input to ask for different kinds of information.")
|
||||
.push(if is_secure {
|
||||
|
|
@ -551,6 +592,11 @@ impl<'a> Step {
|
|||
is_secure,
|
||||
StepMessage::ToggleSecureInput,
|
||||
))
|
||||
.push(checkbox(
|
||||
"Show icon",
|
||||
is_showing_icon,
|
||||
StepMessage::ToggleTextInputIcon,
|
||||
))
|
||||
.push(
|
||||
"A text input produces a message every time it changes. It is \
|
||||
very easy to keep track of its contents:",
|
||||
|
|
|
|||
|
|
@ -13,24 +13,30 @@ use std::fmt;
|
|||
pub fn connect() -> Subscription<Event> {
|
||||
struct Connect;
|
||||
|
||||
subscription::unfold(
|
||||
subscription::channel(
|
||||
std::any::TypeId::of::<Connect>(),
|
||||
State::Disconnected,
|
||||
|state| async move {
|
||||
match state {
|
||||
State::Disconnected => {
|
||||
const ECHO_SERVER: &str = "ws://localhost:3030";
|
||||
100,
|
||||
|mut output| async move {
|
||||
let mut state = State::Disconnected;
|
||||
|
||||
match async_tungstenite::tokio::connect_async(ECHO_SERVER)
|
||||
loop {
|
||||
match &mut state {
|
||||
State::Disconnected => {
|
||||
const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
|
||||
|
||||
match async_tungstenite::tokio::connect_async(
|
||||
ECHO_SERVER,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((websocket, _)) => {
|
||||
let (sender, receiver) = mpsc::channel(100);
|
||||
|
||||
(
|
||||
Some(Event::Connected(Connection(sender))),
|
||||
State::Connected(websocket, receiver),
|
||||
)
|
||||
let _ = output
|
||||
.send(Event::Connected(Connection(sender)))
|
||||
.await;
|
||||
|
||||
state = State::Connected(websocket, receiver);
|
||||
}
|
||||
Err(_) => {
|
||||
tokio::time::sleep(
|
||||
|
|
@ -38,38 +44,36 @@ pub fn connect() -> Subscription<Event> {
|
|||
)
|
||||
.await;
|
||||
|
||||
(Some(Event::Disconnected), State::Disconnected)
|
||||
let _ = output.send(Event::Disconnected).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
State::Connected(mut websocket, mut input) => {
|
||||
State::Connected(websocket, input) => {
|
||||
let mut fused_websocket = websocket.by_ref().fuse();
|
||||
|
||||
futures::select! {
|
||||
received = fused_websocket.select_next_some() => {
|
||||
match received {
|
||||
Ok(tungstenite::Message::Text(message)) => {
|
||||
(
|
||||
Some(Event::MessageReceived(Message::User(message))),
|
||||
State::Connected(websocket, input)
|
||||
)
|
||||
}
|
||||
Ok(_) => {
|
||||
(None, State::Connected(websocket, input))
|
||||
let _ = output.send(Event::MessageReceived(Message::User(message))).await;
|
||||
}
|
||||
Err(_) => {
|
||||
(Some(Event::Disconnected), State::Disconnected)
|
||||
let _ = output.send(Event::Disconnected).await;
|
||||
|
||||
state = State::Disconnected;
|
||||
}
|
||||
Ok(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
message = input.select_next_some() => {
|
||||
let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
|
||||
|
||||
if result.is_ok() {
|
||||
(None, State::Connected(websocket, input))
|
||||
} else {
|
||||
(Some(Event::Disconnected), State::Disconnected)
|
||||
if result.is_err() {
|
||||
let _ = output.send(Event::Disconnected).await;
|
||||
|
||||
state = State::Disconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,11 +125,8 @@ impl Application for WebSocket {
|
|||
};
|
||||
|
||||
let new_message_input = {
|
||||
let mut input = text_input(
|
||||
"Type a message...",
|
||||
&self.new_message,
|
||||
Message::NewMessageChanged,
|
||||
)
|
||||
let mut input = text_input("Type a message...", &self.new_message)
|
||||
.on_input(Message::NewMessageChanged)
|
||||
.padding(10);
|
||||
|
||||
let mut button = button(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ thread-pool = ["futures/thread-pool"]
|
|||
log = "0.4"
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.8"
|
||||
version = "0.9"
|
||||
path = "../core"
|
||||
|
||||
[dependencies.futures]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use crate::core::Hasher;
|
|||
use crate::futures::{Future, Stream};
|
||||
use crate::{BoxStream, MaybeSend};
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::never::Never;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A stream of runtime events.
|
||||
|
|
@ -126,9 +128,9 @@ impl<Message> std::fmt::Debug for Subscription<Message> {
|
|||
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
|
||||
/// to listen to time.
|
||||
///
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch
|
||||
pub trait Recipe {
|
||||
/// The events that will be produced by a [`Subscription`] with this
|
||||
/// [`Recipe`].
|
||||
|
|
@ -317,6 +319,27 @@ where
|
|||
/// [`Stream`] that will call the provided closure to produce every `Message`.
|
||||
///
|
||||
/// The `id` will be used to uniquely identify the [`Subscription`].
|
||||
pub fn unfold<I, T, Fut, Message>(
|
||||
id: I,
|
||||
initial: T,
|
||||
mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
|
||||
) -> Subscription<Message>
|
||||
where
|
||||
I: Hash + 'static,
|
||||
T: MaybeSend + 'static,
|
||||
Fut: Future<Output = (Message, T)> + MaybeSend + 'static,
|
||||
Message: 'static + MaybeSend,
|
||||
{
|
||||
use futures::future::FutureExt;
|
||||
|
||||
run_with_id(
|
||||
id,
|
||||
futures::stream::unfold(initial, move |state| f(state).map(Some)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`Subscription`] that publishes the events sent from a [`Future`]
|
||||
/// to an [`mpsc::Sender`] with the given bounds.
|
||||
///
|
||||
/// # Creating an asynchronous worker with bidirectional communication
|
||||
/// You can leverage this helper to create a [`Subscription`] that spawns
|
||||
|
|
@ -328,9 +351,8 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// use iced_futures::subscription::{self, Subscription};
|
||||
/// use iced_futures::futures;
|
||||
///
|
||||
/// use futures::channel::mpsc;
|
||||
/// use iced_futures::futures::channel::mpsc;
|
||||
/// use iced_futures::futures::sink::SinkExt;
|
||||
///
|
||||
/// pub enum Event {
|
||||
/// Ready(mpsc::Sender<Input>),
|
||||
|
|
@ -351,16 +373,23 @@ where
|
|||
/// fn some_worker() -> Subscription<Event> {
|
||||
/// struct SomeWorker;
|
||||
///
|
||||
/// subscription::unfold(std::any::TypeId::of::<SomeWorker>(), State::Starting, |state| async move {
|
||||
/// match state {
|
||||
/// subscription::channel(std::any::TypeId::of::<SomeWorker>(), 100, |mut output| async move {
|
||||
/// let mut state = State::Starting;
|
||||
///
|
||||
/// loop {
|
||||
/// match &mut state {
|
||||
/// State::Starting => {
|
||||
/// // Create channel
|
||||
/// let (sender, receiver) = mpsc::channel(100);
|
||||
///
|
||||
/// (Some(Event::Ready(sender)), State::Ready(receiver))
|
||||
/// // Send the sender back to the application
|
||||
/// output.send(Event::Ready(sender)).await;
|
||||
///
|
||||
/// // We are ready to receive messages
|
||||
/// state = State::Ready(receiver);
|
||||
/// }
|
||||
/// State::Ready(mut receiver) => {
|
||||
/// use futures::StreamExt;
|
||||
/// State::Ready(receiver) => {
|
||||
/// use iced_futures::futures::StreamExt;
|
||||
///
|
||||
/// // Read next input sent from `Application`
|
||||
/// let input = receiver.select_next_some().await;
|
||||
|
|
@ -369,9 +398,10 @@ where
|
|||
/// Input::DoSomeWork => {
|
||||
/// // Do some async work...
|
||||
///
|
||||
/// // Finally, we can optionally return a message to tell the
|
||||
/// // Finally, we can optionally produce a message to tell the
|
||||
/// // `Application` the work is done
|
||||
/// (Some(Event::WorkFinished), State::Ready(receiver))
|
||||
/// output.send(Event::WorkFinished).await;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
|
|
@ -383,26 +413,29 @@ where
|
|||
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
|
||||
/// connection open.
|
||||
///
|
||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket
|
||||
pub fn unfold<I, T, Fut, Message>(
|
||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket
|
||||
pub fn channel<I, Fut, Message>(
|
||||
id: I,
|
||||
initial: T,
|
||||
mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
|
||||
size: usize,
|
||||
f: impl Fn(mpsc::Sender<Message>) -> Fut + MaybeSend + Sync + 'static,
|
||||
) -> Subscription<Message>
|
||||
where
|
||||
I: Hash + 'static,
|
||||
T: MaybeSend + 'static,
|
||||
Fut: Future<Output = (Option<Message>, T)> + MaybeSend + 'static,
|
||||
Fut: Future<Output = Never> + MaybeSend + 'static,
|
||||
Message: 'static + MaybeSend,
|
||||
{
|
||||
use futures::future::{self, FutureExt};
|
||||
use futures::stream::StreamExt;
|
||||
use futures::stream::{self, StreamExt};
|
||||
|
||||
run_with_id(
|
||||
Subscription::from_recipe(Runner {
|
||||
id,
|
||||
futures::stream::unfold(initial, move |state| f(state).map(Some))
|
||||
.filter_map(future::ready),
|
||||
)
|
||||
spawn: move |_| {
|
||||
let (sender, receiver) = mpsc::channel(size);
|
||||
|
||||
let runner = stream::once(f(sender)).map(|_| unreachable!());
|
||||
|
||||
stream::select(receiver, runner)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
struct Runner<I, F, S, Message>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_graphics"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"
|
||||
|
|
@ -27,7 +27,7 @@ version = "1.4"
|
|||
features = ["derive"]
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.8"
|
||||
version = "0.9"
|
||||
path = "../core"
|
||||
|
||||
[dependencies.tiny-skia]
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ raw-window-handle = "0.5"
|
|||
thiserror = "1"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../graphics"
|
||||
|
||||
[dependencies.iced_wgpu]
|
||||
version = "0.9"
|
||||
version = "0.10"
|
||||
path = "../wgpu"
|
||||
optional = true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_runtime"
|
||||
version = "0.9.1"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A renderer-agnostic library for native GUIs"
|
||||
|
|
@ -14,7 +14,7 @@ debug = []
|
|||
thiserror = "1"
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.8"
|
||||
version = "0.9"
|
||||
path = "../core"
|
||||
|
||||
[dependencies.iced_futures]
|
||||
|
|
|
|||
|
|
@ -12,13 +12,6 @@
|
|||
[`druid`]: https://github.com/xi-editor/druid
|
||||
[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
||||
|
||||
## Installation
|
||||
Add `iced_runtime` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_runtime = "0.9"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
you want to learn about a specific release, check out [the release list].
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
//! - Build a new renderer, see the [renderer] module.
|
||||
//! - Build a custom widget, start at the [`Widget`] trait.
|
||||
//!
|
||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.8/core
|
||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.8/winit
|
||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.9/core
|
||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.9/winit
|
||||
//! [`druid`]: https://github.com/xi-editor/druid
|
||||
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
||||
//! [renderer]: crate::renderer
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ use crate::core::{Element, Layout, Shell};
|
|||
/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
|
||||
/// [`UserInterface`] to integrate Iced in an existing graphical application.
|
||||
///
|
||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_opengl
|
||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_wgpu
|
||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_opengl
|
||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_wgpu
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct UserInterface<'a, Message, Renderer> {
|
||||
root: Element<'a, Message, Renderer>,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ pub use action::Action;
|
|||
|
||||
use crate::command::{self, Command};
|
||||
use crate::core::time::Instant;
|
||||
use crate::core::window::{Event, Mode, UserAttention};
|
||||
use crate::core::window::{Event, Icon, Mode, UserAttention};
|
||||
use crate::futures::subscription::{self, Subscription};
|
||||
|
||||
/// Subscribes to the frames of the window of the running application.
|
||||
|
|
@ -110,3 +110,8 @@ pub fn fetch_id<Message>(
|
|||
) -> Command<Message> {
|
||||
Command::single(command::Action::Window(Action::FetchId(Box::new(f))))
|
||||
}
|
||||
|
||||
/// Changes the [`Icon`] of the window.
|
||||
pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
|
||||
Command::single(command::Action::Window(Action::ChangeIcon(icon)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::window::{Mode, UserAttention};
|
||||
use crate::core::window::{Icon, Mode, UserAttention};
|
||||
use crate::futures::MaybeSend;
|
||||
|
||||
use std::fmt;
|
||||
|
|
@ -78,6 +78,21 @@ pub enum Action<T> {
|
|||
ChangeAlwaysOnTop(bool),
|
||||
/// Fetch an identifier unique to the window.
|
||||
FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
|
||||
/// Changes the window [`Icon`].
|
||||
///
|
||||
/// On Windows and X11, this is typically the small icon in the top-left
|
||||
/// corner of the titlebar.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web / Wayland / macOS:** Unsupported.
|
||||
///
|
||||
/// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
|
||||
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
||||
///
|
||||
/// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That
|
||||
/// said, it's usually in the same ballpark as on Windows.
|
||||
ChangeIcon(Icon),
|
||||
}
|
||||
|
||||
impl<T> Action<T> {
|
||||
|
|
@ -108,6 +123,7 @@ impl<T> Action<T> {
|
|||
Action::ChangeAlwaysOnTop(on_top)
|
||||
}
|
||||
Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
|
||||
Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +158,9 @@ impl<T> fmt::Debug for Action<T> {
|
|||
write!(f, "Action::AlwaysOnTop({on_top})")
|
||||
}
|
||||
Self::FetchId(_) => write!(f, "Action::FetchId"),
|
||||
Self::ChangeIcon(_icon) => {
|
||||
write!(f, "Action::ChangeIcon(icon)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,15 +39,15 @@ pub use crate::style::application::{Appearance, StyleSheet};
|
|||
/// to listen to time.
|
||||
/// - [`todos`], a todos tracker inspired by [TodoMVC].
|
||||
///
|
||||
/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.8/examples
|
||||
/// [`clock`]: https://github.com/iced-rs/iced/tree/0.8/examples/clock
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress
|
||||
/// [`events`]: https://github.com/iced-rs/iced/tree/0.8/examples/events
|
||||
/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.8/examples/game_of_life
|
||||
/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.8/examples/pokedex
|
||||
/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.8/examples/solar_system
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch
|
||||
/// [`todos`]: https://github.com/iced-rs/iced/tree/0.8/examples/todos
|
||||
/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
||||
/// [`clock`]: https://github.com/iced-rs/iced/tree/0.9/examples/clock
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress
|
||||
/// [`events`]: https://github.com/iced-rs/iced/tree/0.9/examples/events
|
||||
/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.9/examples/game_of_life
|
||||
/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.9/examples/pokedex
|
||||
/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.9/examples/solar_system
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch
|
||||
/// [`todos`]: https://github.com/iced-rs/iced/tree/0.9/examples/todos
|
||||
/// [`Sandbox`]: crate::Sandbox
|
||||
/// [`Canvas`]: crate::widget::Canvas
|
||||
/// [PokéAPI]: https://pokeapi.co/
|
||||
|
|
|
|||
10
src/lib.rs
10
src/lib.rs
|
|
@ -24,13 +24,13 @@
|
|||
//! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui
|
||||
//! [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee
|
||||
//! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md
|
||||
//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.8/wgpu
|
||||
//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.8/winit
|
||||
//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.9/wgpu
|
||||
//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.9/winit
|
||||
//! [`dodrio`]: https://github.com/fitzgen/dodrio
|
||||
//! [web runtime]: https://github.com/iced-rs/iced_web
|
||||
//! [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
|
||||
//! [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
||||
//! [repository]: https://github.com/iced-rs/iced
|
||||
//!
|
||||
//! # Overview
|
||||
|
|
@ -235,7 +235,7 @@ pub mod mouse {
|
|||
pub mod subscription {
|
||||
//! Listen to external events in your application.
|
||||
pub use iced_futures::subscription::{
|
||||
events, events_with, run, run_with_id, unfold, Subscription,
|
||||
channel, events, events_with, run, run_with_id, unfold, Subscription,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,19 +34,19 @@ use crate::{Application, Command, Element, Error, Settings, Subscription};
|
|||
/// - [`tour`], a simple UI tour that can run both on native platforms and the
|
||||
/// web!
|
||||
///
|
||||
/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.8/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
|
||||
/// [`counter`]: https://github.com/iced-rs/iced/tree/0.8/examples/counter
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
|
||||
/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid
|
||||
/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.8/examples/progress_bar
|
||||
/// [`styling`]: https://github.com/iced-rs/iced/tree/0.8/examples/styling
|
||||
/// [`svg`]: https://github.com/iced-rs/iced/tree/0.8/examples/svg
|
||||
/// [`tour`]: https://github.com/iced-rs/iced/tree/0.8/examples/tour
|
||||
/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
|
||||
/// [`counter`]: https://github.com/iced-rs/iced/tree/0.9/examples/counter
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
|
||||
/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
|
||||
/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.9/examples/progress_bar
|
||||
/// [`styling`]: https://github.com/iced-rs/iced/tree/0.9/examples/styling
|
||||
/// [`svg`]: https://github.com/iced-rs/iced/tree/0.9/examples/svg
|
||||
/// [`tour`]: https://github.com/iced-rs/iced/tree/0.9/examples/tour
|
||||
/// [`Canvas widget`]: crate::widget::Canvas
|
||||
/// [the overview]: index.html#overview
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
|
||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
|
||||
/// [`Svg` widget]: crate::widget::Svg
|
||||
/// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,52 +1,34 @@
|
|||
//! Attach an icon to the window of your application.
|
||||
use std::fmt;
|
||||
pub use crate::core::window::icon::*;
|
||||
|
||||
use crate::core::window::icon;
|
||||
|
||||
use std::io;
|
||||
|
||||
#[cfg(feature = "image_rs")]
|
||||
#[cfg(feature = "image")]
|
||||
use std::path::Path;
|
||||
|
||||
/// The icon of a window.
|
||||
#[derive(Clone)]
|
||||
pub struct Icon(iced_winit::winit::window::Icon);
|
||||
|
||||
impl fmt::Debug for Icon {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("Icon").field(&format_args!("_")).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
/// Creates an icon from 32bpp RGBA data.
|
||||
pub fn from_rgba(
|
||||
rgba: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Self, Error> {
|
||||
let raw =
|
||||
iced_winit::winit::window::Icon::from_rgba(rgba, width, height)?;
|
||||
|
||||
Ok(Icon(raw))
|
||||
}
|
||||
|
||||
/// Creates an icon from an image file.
|
||||
///
|
||||
/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead.
|
||||
#[cfg(feature = "image_rs")]
|
||||
pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Self, Error> {
|
||||
/// Creates an icon from an image file.
|
||||
///
|
||||
/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead.
|
||||
#[cfg(feature = "image")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
|
||||
pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Icon, Error> {
|
||||
let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8();
|
||||
|
||||
Self::from_rgba(icon.to_vec(), icon.width(), icon.height())
|
||||
}
|
||||
Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?)
|
||||
}
|
||||
|
||||
/// Creates an icon from the content of an image file.
|
||||
///
|
||||
/// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro. \
|
||||
/// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime.
|
||||
#[cfg(feature = "image_rs")]
|
||||
pub fn from_file_data(
|
||||
/// Creates an icon from the content of an image file.
|
||||
///
|
||||
/// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro.
|
||||
/// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime.
|
||||
#[cfg(feature = "image")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
|
||||
pub fn from_file_data(
|
||||
data: &[u8],
|
||||
explicit_format: Option<image_rs::ImageFormat>,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Icon, Error> {
|
||||
let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data));
|
||||
let icon_with_format = match explicit_format {
|
||||
Some(format) => {
|
||||
|
|
@ -58,118 +40,27 @@ impl Icon {
|
|||
|
||||
let pixels = icon_with_format.decode()?.to_rgba8();
|
||||
|
||||
Self::from_rgba(pixels.to_vec(), pixels.width(), pixels.height())
|
||||
}
|
||||
Ok(icon::from_rgba(
|
||||
pixels.to_vec(),
|
||||
pixels.width(),
|
||||
pixels.height(),
|
||||
)?)
|
||||
}
|
||||
|
||||
/// An error produced when using `Icon::from_rgba` with invalid arguments.
|
||||
#[derive(Debug)]
|
||||
/// An error produced when creating an [`Icon`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The provided RGBA data isn't divisble by 4.
|
||||
///
|
||||
/// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels.
|
||||
InvalidData {
|
||||
/// The length of the provided RGBA data.
|
||||
byte_count: usize,
|
||||
},
|
||||
|
||||
/// The number of RGBA pixels does not match the provided dimensions.
|
||||
DimensionsMismatch {
|
||||
/// The provided width.
|
||||
width: u32,
|
||||
/// The provided height.
|
||||
height: u32,
|
||||
/// The amount of pixels of the provided RGBA data.
|
||||
pixel_count: usize,
|
||||
},
|
||||
/// The [`Icon`] is not valid.
|
||||
#[error("The icon is invalid: {0}")]
|
||||
InvalidError(#[from] icon::Error),
|
||||
|
||||
/// The underlying OS failed to create the icon.
|
||||
OsError(io::Error),
|
||||
#[error("The underlying OS failted to create the window icon: {0}")]
|
||||
OsError(#[from] io::Error),
|
||||
|
||||
/// The `image` crate reported an error
|
||||
#[cfg(feature = "image_rs")]
|
||||
ImageError(image_rs::error::ImageError),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(os_error: std::io::Error) -> Self {
|
||||
Error::OsError(os_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<iced_winit::winit::window::BadIcon> for Error {
|
||||
fn from(error: iced_winit::winit::window::BadIcon) -> Self {
|
||||
use iced_winit::winit::window::BadIcon;
|
||||
|
||||
match error {
|
||||
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => {
|
||||
Error::InvalidData { byte_count }
|
||||
}
|
||||
BadIcon::DimensionsVsPixelCount {
|
||||
width,
|
||||
height,
|
||||
pixel_count,
|
||||
..
|
||||
} => Error::DimensionsMismatch {
|
||||
width,
|
||||
height,
|
||||
pixel_count,
|
||||
},
|
||||
BadIcon::OsError(os_error) => Error::OsError(os_error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Icon> for iced_winit::winit::window::Icon {
|
||||
fn from(icon: Icon) -> Self {
|
||||
icon.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image_rs")]
|
||||
impl From<image_rs::error::ImageError> for Error {
|
||||
fn from(image_error: image_rs::error::ImageError) -> Self {
|
||||
Self::ImageError(image_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::InvalidData { byte_count } => {
|
||||
write!(
|
||||
f,
|
||||
"The provided RGBA data (with length {byte_count:?}) isn't divisble by \
|
||||
4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
|
||||
pixels."
|
||||
)
|
||||
}
|
||||
Error::DimensionsMismatch {
|
||||
width,
|
||||
height,
|
||||
pixel_count,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"The number of RGBA pixels ({pixel_count:?}) does not match the provided \
|
||||
dimensions ({width:?}x{height:?})."
|
||||
)
|
||||
}
|
||||
Error::OsError(e) => write!(
|
||||
f,
|
||||
"The underlying OS failed to create the window \
|
||||
icon: {e:?}"
|
||||
),
|
||||
#[cfg(feature = "image_rs")]
|
||||
Error::ImageError(e) => {
|
||||
write!(f, "Unable to create icon from a file: {e:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
Some(self)
|
||||
}
|
||||
/// The `image` crate reported an error.
|
||||
#[cfg(feature = "image")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
|
||||
#[error("Unable to create icon from a file: {0}")]
|
||||
ImageError(#[from] image_rs::error::ImageError),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_style"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "The default set of styles of Iced"
|
||||
|
|
@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
|||
categories = ["gui"]
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.8"
|
||||
version = "0.9"
|
||||
path = "../core"
|
||||
features = ["palette"]
|
||||
|
||||
|
|
|
|||
|
|
@ -37,12 +37,16 @@ pub trait StyleSheet {
|
|||
/// Produces the style of an active scrollbar.
|
||||
fn active(&self, style: &Self::Style) -> Scrollbar;
|
||||
|
||||
/// Produces the style of a hovered scrollbar.
|
||||
fn hovered(&self, style: &Self::Style) -> Scrollbar;
|
||||
/// Produces the style of a scrollbar when the scrollable is being hovered.
|
||||
fn hovered(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> Scrollbar;
|
||||
|
||||
/// Produces the style of a scrollbar that is being dragged.
|
||||
fn dragging(&self, style: &Self::Style) -> Scrollbar {
|
||||
self.hovered(style)
|
||||
self.hovered(style, true)
|
||||
}
|
||||
|
||||
/// Produces the style of an active horizontal scrollbar.
|
||||
|
|
@ -50,13 +54,17 @@ pub trait StyleSheet {
|
|||
self.active(style)
|
||||
}
|
||||
|
||||
/// Produces the style of a hovered horizontal scrollbar.
|
||||
fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
|
||||
self.hovered(style)
|
||||
/// Produces the style of a horizontal scrollbar when the scrollable is being hovered.
|
||||
fn hovered_horizontal(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> Scrollbar {
|
||||
self.hovered(style, is_mouse_over_scrollbar)
|
||||
}
|
||||
|
||||
/// Produces the style of a horizontal scrollbar that is being dragged.
|
||||
fn dragging_horizontal(&self, style: &Self::Style) -> Scrollbar {
|
||||
self.hovered_horizontal(style)
|
||||
self.hovered_horizontal(style, true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,20 @@ use iced_core::Color;
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Appearance {
|
||||
/// The colors of the rail of the slider.
|
||||
pub rail_colors: (Color, Color),
|
||||
pub rail: Rail,
|
||||
/// The appearance of the [`Handle`] of the slider.
|
||||
pub handle: Handle,
|
||||
}
|
||||
|
||||
/// The appearance of a slider rail
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Rail {
|
||||
/// The colors of the rail of the slider.
|
||||
pub colors: (Color, Color),
|
||||
/// The width of the stroke of a slider rail.
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
/// The appearance of the handle of a slider.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Handle {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ pub struct Appearance {
|
|||
pub border_width: f32,
|
||||
/// The border [`Color`] of the text input.
|
||||
pub border_color: Color,
|
||||
/// The icon [`Color`] of the text input.
|
||||
pub icon_color: Color,
|
||||
}
|
||||
|
||||
/// A set of rules that dictate the style of a text input.
|
||||
|
|
@ -31,6 +33,9 @@ pub trait StyleSheet {
|
|||
/// Produces the [`Color`] of the value of a text input.
|
||||
fn value_color(&self, style: &Self::Style) -> Color;
|
||||
|
||||
/// Produces the [`Color`] of the value of a disabled text input.
|
||||
fn disabled_color(&self, style: &Self::Style) -> Color;
|
||||
|
||||
/// Produces the [`Color`] of the selection of a text input.
|
||||
fn selection_color(&self, style: &Self::Style) -> Color;
|
||||
|
||||
|
|
@ -38,4 +43,7 @@ pub trait StyleSheet {
|
|||
fn hovered(&self, style: &Self::Style) -> Appearance {
|
||||
self.focused(style)
|
||||
}
|
||||
|
||||
/// Produces the style of a disabled text input.
|
||||
fn disabled(&self, style: &Self::Style) -> Appearance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -416,10 +416,13 @@ impl slider::StyleSheet for Theme {
|
|||
};
|
||||
|
||||
slider::Appearance {
|
||||
rail_colors: (
|
||||
rail: slider::Rail {
|
||||
colors: (
|
||||
palette.primary.base.color,
|
||||
palette.primary.base.color,
|
||||
Color::TRANSPARENT,
|
||||
),
|
||||
width: 2.0,
|
||||
},
|
||||
handle: slider::Handle {
|
||||
color: palette.background.base.color,
|
||||
border_color: palette.primary.base.color,
|
||||
|
|
@ -906,9 +909,14 @@ impl scrollable::StyleSheet for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
fn hovered(&self, style: &Self::Style) -> scrollable::Scrollbar {
|
||||
fn hovered(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> scrollable::Scrollbar {
|
||||
match style {
|
||||
Scrollable::Default => {
|
||||
if is_mouse_over_scrollbar {
|
||||
let palette = self.extended_palette();
|
||||
|
||||
scrollable::Scrollbar {
|
||||
|
|
@ -923,14 +931,19 @@ impl scrollable::StyleSheet for Theme {
|
|||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.active(style)
|
||||
}
|
||||
}
|
||||
Scrollable::Custom(custom) => {
|
||||
custom.hovered(self, is_mouse_over_scrollbar)
|
||||
}
|
||||
Scrollable::Custom(custom) => custom.hovered(self),
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging(&self, style: &Self::Style) -> scrollable::Scrollbar {
|
||||
match style {
|
||||
Scrollable::Default => self.hovered(style),
|
||||
Scrollable::Default => self.hovered(style, true),
|
||||
Scrollable::Custom(custom) => custom.dragging(self),
|
||||
}
|
||||
}
|
||||
|
|
@ -942,10 +955,16 @@ impl scrollable::StyleSheet for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
fn hovered_horizontal(&self, style: &Self::Style) -> scrollable::Scrollbar {
|
||||
fn hovered_horizontal(
|
||||
&self,
|
||||
style: &Self::Style,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
) -> scrollable::Scrollbar {
|
||||
match style {
|
||||
Scrollable::Default => self.hovered(style),
|
||||
Scrollable::Custom(custom) => custom.hovered_horizontal(self),
|
||||
Scrollable::Default => self.hovered(style, is_mouse_over_scrollbar),
|
||||
Scrollable::Custom(custom) => {
|
||||
custom.hovered_horizontal(self, is_mouse_over_scrollbar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -954,7 +973,7 @@ impl scrollable::StyleSheet for Theme {
|
|||
style: &Self::Style,
|
||||
) -> scrollable::Scrollbar {
|
||||
match style {
|
||||
Scrollable::Default => self.hovered_horizontal(style),
|
||||
Scrollable::Default => self.hovered_horizontal(style, true),
|
||||
Scrollable::Custom(custom) => custom.dragging_horizontal(self),
|
||||
}
|
||||
}
|
||||
|
|
@ -1012,6 +1031,7 @@ impl text_input::StyleSheet for Theme {
|
|||
border_radius: 2.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.background.strong.color,
|
||||
icon_color: palette.background.weak.text,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1027,6 +1047,7 @@ impl text_input::StyleSheet for Theme {
|
|||
border_radius: 2.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.background.base.text,
|
||||
icon_color: palette.background.weak.text,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1042,6 +1063,7 @@ impl text_input::StyleSheet for Theme {
|
|||
border_radius: 2.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.primary.strong.color,
|
||||
icon_color: palette.background.weak.text,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1074,4 +1096,28 @@ impl text_input::StyleSheet for Theme {
|
|||
|
||||
palette.primary.weak.color
|
||||
}
|
||||
|
||||
fn disabled(&self, style: &Self::Style) -> text_input::Appearance {
|
||||
if let TextInput::Custom(custom) = style {
|
||||
return custom.disabled(self);
|
||||
}
|
||||
|
||||
let palette = self.extended_palette();
|
||||
|
||||
text_input::Appearance {
|
||||
background: palette.background.weak.color.into(),
|
||||
border_radius: 2.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.background.strong.color,
|
||||
icon_color: palette.background.strong.color,
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled_color(&self, style: &Self::Style) -> Color {
|
||||
if let TextInput::Custom(custom) = style {
|
||||
return custom.disabled_color(self);
|
||||
}
|
||||
|
||||
self.placeholder_color(style)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ kurbo = "0.9"
|
|||
log = "0.4"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../graphics"
|
||||
features = ["tiny-skia"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_wgpu"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A wgpu renderer for Iced"
|
||||
|
|
@ -38,7 +38,7 @@ version = "1.9"
|
|||
features = ["derive"]
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../graphics"
|
||||
|
||||
[dependencies.glyphon]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Currently, `iced_wgpu` supports the following primitives:
|
|||
Add `iced_wgpu` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_wgpu = "0.9"
|
||||
iced_wgpu = "0.10"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ impl Pipeline {
|
|||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu::image::shader"),
|
||||
label: Some("iced_wgpu image shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
include_str!("shader/image.wgsl"),
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
//! - Meshes of triangles, useful to draw geometry freely.
|
||||
//!
|
||||
//! [Iced]: https://github.com/iced-rs/iced
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
|
||||
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ impl Pipeline {
|
|||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu::quad::shader"),
|
||||
label: Some("iced_wgpu quad shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
include_str!("shader/quad.wgsl"),
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ mod solid {
|
|||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu::triangle::solid create shader module",
|
||||
"iced_wgpu triangle solid create shader module",
|
||||
),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
std::borrow::Cow::Borrowed(include_str!(
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ impl Blit {
|
|||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu::triangle::blit_shader"),
|
||||
label: Some("iced_wgpu triangle blit_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
include_str!("../shader/blit.wgsl"),
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -66,7 +66,19 @@ impl<Theme> Compositor<Theme> {
|
|||
log::info!("Selected: {:#?}", adapter.get_info());
|
||||
|
||||
let format = compatible_surface.as_ref().and_then(|surface| {
|
||||
surface.get_capabilities(&adapter).formats.first().copied()
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
capabilities
|
||||
.formats
|
||||
.iter()
|
||||
.filter(|format| format.describe().srgb)
|
||||
.copied()
|
||||
.next()
|
||||
.or_else(|| {
|
||||
log::warn!("No sRGB format found!");
|
||||
|
||||
capabilities.formats.first().copied()
|
||||
})
|
||||
})?;
|
||||
|
||||
log::info!("Selected format: {:?}", format);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ num-traits = "0.2"
|
|||
thiserror = "1"
|
||||
|
||||
[dependencies.iced_runtime]
|
||||
version = "0.9"
|
||||
version = "0.1"
|
||||
path = "../runtime"
|
||||
|
||||
[dependencies.iced_renderer]
|
||||
|
|
@ -24,7 +24,7 @@ version = "0.1"
|
|||
path = "../renderer"
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../style"
|
||||
|
||||
[dependencies.ouroboros]
|
||||
|
|
|
|||
|
|
@ -15,17 +15,6 @@ use crate::{Row, Text};
|
|||
|
||||
pub use iced_style::checkbox::{Appearance, StyleSheet};
|
||||
|
||||
/// The icon in a [`Checkbox`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Icon<Font> {
|
||||
/// Font that will be used to display the `code_point`,
|
||||
pub font: Font,
|
||||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// Font size of the content.
|
||||
pub size: Option<f32>,
|
||||
}
|
||||
|
||||
/// A box that can be checked.
|
||||
///
|
||||
/// # Example
|
||||
|
|
@ -321,3 +310,14 @@ where
|
|||
Element::new(checkbox)
|
||||
}
|
||||
}
|
||||
|
||||
/// The icon in a [`Checkbox`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Icon<Font> {
|
||||
/// Font that will be used to display the `code_point`,
|
||||
pub font: Font,
|
||||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// Font size of the content.
|
||||
pub size: Option<f32>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::text::{self, Text};
|
|||
use crate::text_input::{self, TextInput};
|
||||
use crate::toggler::{self, Toggler};
|
||||
use crate::tooltip::{self, Tooltip};
|
||||
use crate::{Column, Row, Space, VerticalSlider};
|
||||
use crate::{Column, MouseArea, Row, Space, VerticalSlider};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ops::RangeInclusive;
|
||||
|
|
@ -163,7 +163,7 @@ where
|
|||
Renderer::Theme: radio::StyleSheet,
|
||||
V: Copy + Eq,
|
||||
{
|
||||
Radio::new(value, label, selected, on_click)
|
||||
Radio::new(label, value, selected, on_click)
|
||||
}
|
||||
|
||||
/// Creates a new [`Toggler`].
|
||||
|
|
@ -187,14 +187,13 @@ where
|
|||
pub fn text_input<'a, Message, Renderer>(
|
||||
placeholder: &str,
|
||||
value: &str,
|
||||
on_change: impl Fn(String) -> Message + 'a,
|
||||
) -> TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: core::text::Renderer,
|
||||
Renderer::Theme: text_input::StyleSheet,
|
||||
{
|
||||
TextInput::new(placeholder, value, on_change)
|
||||
TextInput::new(placeholder, value)
|
||||
}
|
||||
|
||||
/// Creates a new [`Slider`].
|
||||
|
|
@ -360,3 +359,13 @@ where
|
|||
{
|
||||
Command::widget(operation::focusable::focus_next())
|
||||
}
|
||||
|
||||
/// A container intercepting mouse events.
|
||||
pub fn mouse_area<'a, Message, Renderer>(
|
||||
widget: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> MouseArea<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
MouseArea::new(widget)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use crate::core::{
|
|||
use ouroboros::self_referencing;
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A reusable, custom widget that uses The Elm Architecture.
|
||||
///
|
||||
|
|
@ -58,6 +59,8 @@ pub trait Component<Message, Renderer> {
|
|||
}
|
||||
}
|
||||
|
||||
struct Tag<T>(T);
|
||||
|
||||
/// Turns an implementor of [`Component`] into an [`Element`] that can be
|
||||
/// embedded in any application.
|
||||
pub fn view<'a, C, Message, Renderer>(
|
||||
|
|
@ -79,11 +82,13 @@ where
|
|||
}
|
||||
.build(),
|
||||
)),
|
||||
tree: RefCell::new(Rc::new(RefCell::new(None))),
|
||||
})
|
||||
}
|
||||
|
||||
struct Instance<'a, Message, Renderer, Event, S> {
|
||||
state: RefCell<Option<State<'a, Message, Renderer, Event, S>>>,
|
||||
tree: RefCell<Rc<RefCell<Option<Tree>>>>,
|
||||
}
|
||||
|
||||
#[self_referencing]
|
||||
|
|
@ -100,40 +105,91 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> {
|
|||
|
||||
impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S>
|
||||
where
|
||||
S: Default,
|
||||
S: Default + 'static,
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn rebuild_element(&self, state: &S) {
|
||||
let heads = self.state.borrow_mut().take().unwrap().into_heads();
|
||||
fn diff_self(&self) {
|
||||
self.with_element(|element| {
|
||||
self.tree
|
||||
.borrow_mut()
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.diff_children(std::slice::from_ref(&element));
|
||||
});
|
||||
}
|
||||
|
||||
fn rebuild_element_if_necessary(&self) {
|
||||
let inner = self.state.borrow_mut().take().unwrap();
|
||||
if inner.borrow_element().is_none() {
|
||||
let heads = inner.into_heads();
|
||||
|
||||
*self.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |component| Some(component.view(state)),
|
||||
element_builder: |component| {
|
||||
Some(
|
||||
component.view(
|
||||
self.tree
|
||||
.borrow()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.state
|
||||
.downcast_ref::<S>(),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
self.diff_self();
|
||||
} else {
|
||||
*self.state.borrow_mut() = Some(inner);
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild_element_with_operation(
|
||||
&self,
|
||||
state: &mut S,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
let heads = self.state.borrow_mut().take().unwrap().into_heads();
|
||||
|
||||
heads.component.operate(state, operation);
|
||||
heads.component.operate(
|
||||
self.tree
|
||||
.borrow_mut()
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.state
|
||||
.downcast_mut(),
|
||||
operation,
|
||||
);
|
||||
|
||||
*self.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |component| Some(component.view(state)),
|
||||
element_builder: |component| {
|
||||
Some(
|
||||
component.view(
|
||||
self.tree
|
||||
.borrow()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.state
|
||||
.downcast_ref(),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
self.diff_self();
|
||||
}
|
||||
|
||||
fn with_element<T>(
|
||||
|
|
@ -147,6 +203,7 @@ where
|
|||
&self,
|
||||
f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T,
|
||||
) -> T {
|
||||
self.rebuild_element_if_necessary();
|
||||
self.state
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
|
|
@ -162,24 +219,27 @@ where
|
|||
Renderer: core::Renderer,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
struct Tag<T>(T);
|
||||
tree::Tag::of::<Tag<S>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(S::default())
|
||||
let state = Rc::new(RefCell::new(Some(Tree {
|
||||
tag: tree::Tag::of::<Tag<S>>(),
|
||||
state: tree::State::new(S::default()),
|
||||
children: vec![Tree::empty()],
|
||||
})));
|
||||
*self.tree.borrow_mut() = state.clone();
|
||||
tree::State::new(state)
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.rebuild_element(&S::default());
|
||||
self.with_element(|element| vec![Tree::new(element)])
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
self.rebuild_element(tree.state.downcast_ref());
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
})
|
||||
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
|
||||
*self.tree.borrow_mut() = tree.clone();
|
||||
self.rebuild_element_if_necessary();
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -213,9 +273,10 @@ where
|
|||
let mut local_messages = Vec::new();
|
||||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
||||
let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
|
||||
let event_status = self.with_element_mut(|element| {
|
||||
element.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
&mut t.borrow_mut().as_mut().unwrap().children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -235,9 +296,10 @@ where
|
|||
let mut heads = self.state.take().unwrap().into_heads();
|
||||
|
||||
for message in local_messages.into_iter().filter_map(|message| {
|
||||
heads
|
||||
.component
|
||||
.update(tree.state.downcast_mut::<S>(), message)
|
||||
heads.component.update(
|
||||
t.borrow_mut().as_mut().unwrap().state.downcast_mut(),
|
||||
message,
|
||||
)
|
||||
}) {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
|
@ -247,17 +309,11 @@ where
|
|||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |state| {
|
||||
Some(state.view(tree.state.downcast_ref::<S>()))
|
||||
},
|
||||
element_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
));
|
||||
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
||||
|
|
@ -271,10 +327,7 @@ where
|
|||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.rebuild_element_with_operation(
|
||||
tree.state.downcast_mut(),
|
||||
operation,
|
||||
);
|
||||
self.rebuild_element_with_operation(operation);
|
||||
|
||||
struct MapOperation<'a, B> {
|
||||
operation: &'a mut dyn widget::Operation<B>,
|
||||
|
|
@ -308,13 +361,28 @@ where
|
|||
) {
|
||||
self.operation.text_input(state, id);
|
||||
}
|
||||
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
state: &mut dyn widget::operation::Scrollable,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.scrollable(state, id);
|
||||
}
|
||||
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element));
|
||||
fn custom(
|
||||
&mut self,
|
||||
state: &mut dyn std::any::Any,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.custom(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
let tree = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
|
||||
self.with_element(|element| {
|
||||
element.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
&mut tree.borrow_mut().as_mut().unwrap().children[0],
|
||||
layout,
|
||||
renderer,
|
||||
&mut MapOperation { operation },
|
||||
|
|
@ -332,9 +400,10 @@ where
|
|||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
|
||||
self.with_element(|element| {
|
||||
element.as_widget().draw(
|
||||
&tree.children[0],
|
||||
&tree.borrow().as_ref().unwrap().children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -353,9 +422,10 @@ where
|
|||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
|
||||
self.with_element(|element| {
|
||||
element.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
&tree.borrow().as_ref().unwrap().children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -370,7 +440,15 @@ where
|
|||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let overlay = OverlayBuilder {
|
||||
self.rebuild_element_if_necessary();
|
||||
let tree = tree
|
||||
.state
|
||||
.downcast_mut::<Rc<RefCell<Option<Tree>>>>()
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.unwrap();
|
||||
let overlay = Overlay(Some(
|
||||
InnerBuilder {
|
||||
instance: self,
|
||||
tree,
|
||||
types: PhantomData,
|
||||
|
|
@ -386,9 +464,10 @@ where
|
|||
)
|
||||
},
|
||||
}
|
||||
.build();
|
||||
.build(),
|
||||
));
|
||||
|
||||
let has_overlay = overlay.with_overlay(|overlay| {
|
||||
let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| {
|
||||
overlay.as_ref().map(overlay::Element::position)
|
||||
});
|
||||
|
||||
|
|
@ -403,10 +482,24 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, 'b, Message, Renderer, Event, S>(
|
||||
Option<Inner<'a, 'b, Message, Renderer, Event, S>>,
|
||||
);
|
||||
|
||||
impl<'a, 'b, Message, Renderer, Event, S> Drop
|
||||
for Overlay<'a, 'b, Message, Renderer, Event, S>
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(heads) = self.0.take().map(|inner| inner.into_heads()) {
|
||||
*heads.instance.tree.borrow_mut().borrow_mut() = Some(heads.tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[self_referencing]
|
||||
struct Overlay<'a, 'b, Message, Renderer, Event, S> {
|
||||
struct Inner<'a, 'b, Message, Renderer, Event, S> {
|
||||
instance: &'a mut Instance<'b, Message, Renderer, Event, S>,
|
||||
tree: &'a mut Tree,
|
||||
tree: Tree,
|
||||
types: PhantomData<(Message, Event, S)>,
|
||||
|
||||
#[borrows(mut instance, mut tree)]
|
||||
|
|
@ -426,6 +519,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
|
|||
f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.overlay
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow_overlay()
|
||||
|
|
@ -438,6 +534,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
|
|||
f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.overlay
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.0
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_overlay_mut(|overlay| overlay.as_mut().map(f))
|
||||
|
|
@ -523,42 +622,37 @@ where
|
|||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||
|
||||
if !local_messages.is_empty() {
|
||||
let overlay = self.overlay.take().unwrap().into_heads();
|
||||
let mut heads = overlay.instance.state.take().unwrap().into_heads();
|
||||
let mut inner =
|
||||
self.overlay.take().unwrap().0.take().unwrap().into_heads();
|
||||
let mut heads = inner.instance.state.take().unwrap().into_heads();
|
||||
|
||||
for message in local_messages.into_iter().filter_map(|message| {
|
||||
heads
|
||||
.component
|
||||
.update(overlay.tree.state.downcast_mut::<S>(), message)
|
||||
.update(inner.tree.state.downcast_mut(), message)
|
||||
}) {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
||||
*overlay.instance.state.borrow_mut() = Some(
|
||||
*inner.instance.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |state| {
|
||||
Some(state.view(overlay.tree.state.downcast_ref::<S>()))
|
||||
},
|
||||
element_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
|
||||
overlay.instance.with_element(|element| {
|
||||
overlay.tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
self.overlay = Some(
|
||||
OverlayBuilder {
|
||||
instance: overlay.instance,
|
||||
tree: overlay.tree,
|
||||
self.overlay = Some(Overlay(Some(
|
||||
InnerBuilder {
|
||||
instance: inner.instance,
|
||||
tree: inner.tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |_, _| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
)));
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub use iced_runtime::core;
|
|||
pub use iced_style as style;
|
||||
|
||||
mod column;
|
||||
mod mouse_area;
|
||||
mod row;
|
||||
|
||||
pub mod button;
|
||||
|
|
@ -63,6 +64,8 @@ pub use column::Column;
|
|||
#[doc(no_inline)]
|
||||
pub use container::Container;
|
||||
#[doc(no_inline)]
|
||||
pub use mouse_area::MouseArea;
|
||||
#[doc(no_inline)]
|
||||
pub use pane_grid::PaneGrid;
|
||||
#[doc(no_inline)]
|
||||
pub use pick_list::PickList;
|
||||
|
|
|
|||
311
widget/src/mouse_area.rs
Normal file
311
widget/src/mouse_area.rs
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
//! A container for capturing mouse events.
|
||||
|
||||
use crate::core::event::{self, Event};
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::touch;
|
||||
use crate::core::widget::{tree, Operation, Tree};
|
||||
use crate::core::{
|
||||
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget,
|
||||
};
|
||||
|
||||
/// Emit messages on mouse events.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct MouseArea<'a, Message, Renderer> {
|
||||
content: Element<'a, Message, Renderer>,
|
||||
on_press: Option<Message>,
|
||||
on_release: Option<Message>,
|
||||
on_right_press: Option<Message>,
|
||||
on_right_release: Option<Message>,
|
||||
on_middle_press: Option<Message>,
|
||||
on_middle_release: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
|
||||
/// The message to emit on a left button press.
|
||||
#[must_use]
|
||||
pub fn on_press(mut self, message: Message) -> Self {
|
||||
self.on_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a left button release.
|
||||
#[must_use]
|
||||
pub fn on_release(mut self, message: Message) -> Self {
|
||||
self.on_release = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a right button press.
|
||||
#[must_use]
|
||||
pub fn on_right_press(mut self, message: Message) -> Self {
|
||||
self.on_right_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a right button release.
|
||||
#[must_use]
|
||||
pub fn on_right_release(mut self, message: Message) -> Self {
|
||||
self.on_right_release = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a middle button press.
|
||||
#[must_use]
|
||||
pub fn on_middle_press(mut self, message: Message) -> Self {
|
||||
self.on_middle_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a middle button release.
|
||||
#[must_use]
|
||||
pub fn on_middle_release(mut self, message: Message) -> Self {
|
||||
self.on_middle_release = Some(message);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Local state of the [`MouseArea`].
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
// TODO: Support on_mouse_enter and on_mouse_exit
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
|
||||
/// Creates a [`MouseArea`] with the given content.
|
||||
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
MouseArea {
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
on_release: None,
|
||||
on_right_press: None,
|
||||
on_right_release: None,
|
||||
on_middle_press: None,
|
||||
on_middle_release: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for MouseArea<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content));
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.content.as_widget().height()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
if let event::Status::Captured = self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
) {
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
||||
update(self, &event, layout, cursor_position, shell)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
renderer_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + renderer::Renderer,
|
||||
{
|
||||
fn from(
|
||||
area: MouseArea<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(area)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
|
||||
/// accordingly.
|
||||
fn update<Message: Clone, Renderer>(
|
||||
widget: &mut MouseArea<'_, Message, Renderer>,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
if !layout.bounds().contains(cursor_position) {
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_press.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_release.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. }) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_press.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
|
||||
event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_release.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Right,
|
||||
)) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_press.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(
|
||||
mouse::Button::Middle,
|
||||
)) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_release.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Middle,
|
||||
)) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
||||
//! drag and drop, and hotkey support.
|
||||
//!
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
|
||||
mod axis;
|
||||
mod configuration;
|
||||
mod content;
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ pub use iced_style::radio::{Appearance, StyleSheet};
|
|||
/// # type Radio<Message> =
|
||||
/// # iced_widget::Radio<Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
|
||||
/// #
|
||||
/// # use iced_widget::column;
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// pub enum Choice {
|
||||
/// A,
|
||||
/// B,
|
||||
/// C,
|
||||
/// All,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -35,12 +38,36 @@ pub use iced_style::radio::{Appearance, StyleSheet};
|
|||
///
|
||||
/// let selected_choice = Some(Choice::A);
|
||||
///
|
||||
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
|
||||
/// let a = Radio::new(
|
||||
/// "A",
|
||||
/// Choice::A,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected,
|
||||
/// );
|
||||
///
|
||||
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
|
||||
/// let b = Radio::new(
|
||||
/// "B",
|
||||
/// Choice::B,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected,
|
||||
/// );
|
||||
///
|
||||
/// let c = Radio::new(
|
||||
/// "C",
|
||||
/// Choice::C,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected,
|
||||
/// );
|
||||
///
|
||||
/// let all = Radio::new(
|
||||
/// "All of the above",
|
||||
/// Choice::All,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected
|
||||
/// );
|
||||
///
|
||||
/// let content = column![a, b, c, all];
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Radio<Message, Renderer = crate::Renderer>
|
||||
where
|
||||
|
|
@ -79,8 +106,8 @@ where
|
|||
/// * a function that will be called when the [`Radio`] is selected. It
|
||||
/// receives the value of the radio and must produce a `Message`.
|
||||
pub fn new<F, V>(
|
||||
value: V,
|
||||
label: impl Into<String>,
|
||||
value: V,
|
||||
selected: Option<V>,
|
||||
f: F,
|
||||
) -> Self
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
id: Option<Id>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
vertical: Properties,
|
||||
horizontal: Option<Properties>,
|
||||
|
|
@ -44,6 +45,7 @@ where
|
|||
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
Scrollable {
|
||||
id: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
vertical: Properties::default(),
|
||||
horizontal: None,
|
||||
|
|
@ -59,6 +61,12 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Scrollable`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Scrollable`].
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
|
|
@ -167,7 +175,7 @@ where
|
|||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
|
|
@ -182,7 +190,7 @@ where
|
|||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
Widget::<Message, Renderer>::width(self),
|
||||
self.width,
|
||||
self.height,
|
||||
self.horizontal.is_some(),
|
||||
|renderer, limits| {
|
||||
|
|
@ -391,15 +399,7 @@ pub fn layout<Renderer>(
|
|||
horizontal_enabled: bool,
|
||||
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||
) -> layout::Node {
|
||||
let limits = limits
|
||||
.max_height(f32::INFINITY)
|
||||
.max_width(if horizontal_enabled {
|
||||
f32::INFINITY
|
||||
} else {
|
||||
limits.max().width
|
||||
})
|
||||
.width(width)
|
||||
.height(height);
|
||||
let limits = limits.width(width).height(height);
|
||||
|
||||
let child_limits = layout::Limits::new(
|
||||
Size::new(limits.min().width, 0.0),
|
||||
|
|
@ -851,8 +851,8 @@ pub fn draw<Renderer>(
|
|||
if let Some(scrollbar) = scrollbars.y {
|
||||
let style = if state.y_scroller_grabbed_at.is_some() {
|
||||
theme.dragging(style)
|
||||
} else if mouse_over_y_scrollbar {
|
||||
theme.hovered(style)
|
||||
} else if mouse_over_scrollable {
|
||||
theme.hovered(style, mouse_over_y_scrollbar)
|
||||
} else {
|
||||
theme.active(style)
|
||||
};
|
||||
|
|
@ -864,8 +864,8 @@ pub fn draw<Renderer>(
|
|||
if let Some(scrollbar) = scrollbars.x {
|
||||
let style = if state.x_scroller_grabbed_at.is_some() {
|
||||
theme.dragging_horizontal(style)
|
||||
} else if mouse_over_x_scrollbar {
|
||||
theme.hovered_horizontal(style)
|
||||
} else if mouse_over_scrollable {
|
||||
theme.hovered_horizontal(style, mouse_over_x_scrollbar)
|
||||
} else {
|
||||
theme.active_horizontal(style)
|
||||
};
|
||||
|
|
@ -889,7 +889,7 @@ pub fn draw<Renderer>(
|
|||
}
|
||||
|
||||
fn notify_on_scroll<Message>(
|
||||
state: &State,
|
||||
state: &mut State,
|
||||
on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
|
|
@ -910,7 +910,23 @@ fn notify_on_scroll<Message>(
|
|||
.absolute(bounds.height, content_bounds.height)
|
||||
/ (content_bounds.height - bounds.height);
|
||||
|
||||
shell.publish(on_scroll(RelativeOffset { x, y }))
|
||||
let new_offset = RelativeOffset { x, y };
|
||||
|
||||
// Don't publish redundant offsets to shell
|
||||
if let Some(prev_offset) = state.last_notified {
|
||||
let unchanged = |a: f32, b: f32| {
|
||||
(a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
|
||||
};
|
||||
|
||||
if unchanged(prev_offset.x, new_offset.x)
|
||||
&& unchanged(prev_offset.y, new_offset.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shell.publish(on_scroll(new_offset));
|
||||
state.last_notified = Some(new_offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -923,6 +939,7 @@ pub struct State {
|
|||
offset_x: Offset,
|
||||
x_scroller_grabbed_at: Option<f32>,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
last_notified: Option<RelativeOffset>,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
|
|
@ -934,6 +951,7 @@ impl Default for State {
|
|||
offset_x: Offset::Absolute(0.0),
|
||||
x_scroller_grabbed_at: None,
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
last_notified: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ use crate::core::renderer;
|
|||
use crate::core::touch;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
|
||||
Rectangle, Shell, Size, Widget,
|
||||
Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
|
||||
Size, Widget,
|
||||
};
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
|
||||
pub use iced_style::slider::{
|
||||
Appearance, Handle, HandleShape, Rail, StyleSheet,
|
||||
};
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
|
|
@ -366,38 +368,6 @@ pub fn draw<T, R>(
|
|||
style_sheet.active(style)
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y - 1.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail_colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y + 1.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(style.rail_colors.1),
|
||||
);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) = match style
|
||||
.handle
|
||||
.shape
|
||||
|
|
@ -416,17 +386,49 @@ pub fn draw<T, R>(
|
|||
(start.into() as f32, end.into() as f32)
|
||||
};
|
||||
|
||||
let handle_offset = if range_start >= range_end {
|
||||
let offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.width - handle_width) * (value - range_start)
|
||||
(bounds.width - handle_width / 2.0) * (value - range_start)
|
||||
/ (range_end - range_start)
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + bounds.height / 2.0;
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
x: bounds.x,
|
||||
y: rail_y - style.rail.width / 2.0,
|
||||
width: offset + handle_width / 2.0,
|
||||
height: style.rail.width,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + offset + handle_width / 2.0,
|
||||
y: rail_y - style.rail.width / 2.0,
|
||||
width: bounds.width - offset,
|
||||
height: style.rail.width,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.1,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + offset,
|
||||
y: rail_y - handle_height / 2.0,
|
||||
width: handle_width,
|
||||
height: handle_height,
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
|
|||
/// let input = TextInput::new(
|
||||
/// "This is the placeholder...",
|
||||
/// value,
|
||||
/// Message::TextInputChanged,
|
||||
/// )
|
||||
/// .on_input(Message::TextInputChanged)
|
||||
/// .padding(10);
|
||||
/// ```
|
||||
/// 
|
||||
|
|
@ -68,9 +68,10 @@ where
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
on_change: Box<dyn Fn(String) -> Message + 'a>,
|
||||
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||
on_submit: Option<Message>,
|
||||
icon: Option<Icon<Renderer::Font>>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +85,8 @@ where
|
|||
///
|
||||
/// It expects:
|
||||
/// - a placeholder,
|
||||
/// - the current value, and
|
||||
/// - a function that produces a message when the [`TextInput`] changes.
|
||||
pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
|
||||
where
|
||||
F: 'a + Fn(String) -> Message,
|
||||
{
|
||||
/// - the current value
|
||||
pub fn new(placeholder: &str, value: &str) -> Self {
|
||||
TextInput {
|
||||
id: None,
|
||||
placeholder: String::from(placeholder),
|
||||
|
|
@ -99,9 +96,10 @@ where
|
|||
width: Length::Fill,
|
||||
padding: Padding::new(5.0),
|
||||
size: None,
|
||||
on_change: Box::new(on_change),
|
||||
on_input: None,
|
||||
on_paste: None,
|
||||
on_submit: None,
|
||||
icon: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +116,25 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when some text is typed into
|
||||
/// the [`TextInput`].
|
||||
///
|
||||
/// If this method is not called, the [`TextInput`] will be disabled.
|
||||
pub fn on_input<F>(mut self, callback: F) -> Self
|
||||
where
|
||||
F: 'a + Fn(String) -> Message,
|
||||
{
|
||||
self.on_input = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when the [`TextInput`] is
|
||||
/// focused and the enter key is pressed.
|
||||
pub fn on_submit(mut self, message: Message) -> Self {
|
||||
self.on_submit = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when some text is pasted into
|
||||
/// the [`TextInput`].
|
||||
pub fn on_paste(
|
||||
|
|
@ -135,6 +152,13 @@ where
|
|||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Icon`] of the [`TextInput`].
|
||||
pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`TextInput`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
|
|
@ -153,13 +177,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when the [`TextInput`] is
|
||||
/// focused and the enter key is pressed.
|
||||
pub fn on_submit(mut self, message: Message) -> Self {
|
||||
self.on_submit = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`TextInput`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
|
|
@ -192,7 +209,9 @@ where
|
|||
&self.placeholder,
|
||||
self.size,
|
||||
self.font,
|
||||
self.on_input.is_none(),
|
||||
self.is_secure,
|
||||
self.icon.as_ref(),
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
|
@ -213,6 +232,18 @@ where
|
|||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
// Unfocus text input if it becomes disabled
|
||||
if self.on_input.is_none() {
|
||||
state.last_click = None;
|
||||
state.is_focused = None;
|
||||
state.is_pasting = None;
|
||||
state.is_dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -226,7 +257,14 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(renderer, limits, self.width, self.padding, self.size)
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.padding,
|
||||
self.size,
|
||||
self.icon.as_ref(),
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -263,7 +301,7 @@ where
|
|||
self.size,
|
||||
self.font,
|
||||
self.is_secure,
|
||||
self.on_change.as_ref(),
|
||||
self.on_input.as_deref(),
|
||||
self.on_paste.as_deref(),
|
||||
&self.on_submit,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
|
|
@ -290,7 +328,9 @@ where
|
|||
&self.placeholder,
|
||||
self.size,
|
||||
self.font,
|
||||
self.on_input.is_none(),
|
||||
self.is_secure,
|
||||
self.icon.as_ref(),
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
|
@ -303,7 +343,7 @@ where
|
|||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
mouse_interaction(layout, cursor_position, self.on_input.is_none())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -321,6 +361,30 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The content of the [`Icon`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Icon<Font> {
|
||||
/// The font that will be used to display the `code_point`.
|
||||
pub font: Font,
|
||||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// The font size of the content.
|
||||
pub size: Option<f32>,
|
||||
/// The spacing between the [`Icon`] and the text in a [`TextInput`].
|
||||
pub spacing: f32,
|
||||
/// The side of a [`TextInput`] where to display the [`Icon`].
|
||||
pub side: Side,
|
||||
}
|
||||
|
||||
/// The side of a [`TextInput`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Side {
|
||||
/// The left side of a [`TextInput`].
|
||||
Left,
|
||||
/// The right side of a [`TextInput`].
|
||||
Right,
|
||||
}
|
||||
|
||||
/// The identifier of a [`TextInput`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Id(widget::Id);
|
||||
|
|
@ -383,6 +447,7 @@ pub fn layout<Renderer>(
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
icon: Option<&Icon<Renderer::Font>>,
|
||||
) -> layout::Node
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
|
|
@ -391,10 +456,51 @@ where
|
|||
let padding = padding.fit(Size::ZERO, limits.max());
|
||||
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
|
||||
|
||||
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
||||
let text_bounds = limits.resolve(Size::ZERO);
|
||||
|
||||
if let Some(icon) = icon {
|
||||
let icon_width = renderer.measure_width(
|
||||
&icon.code_point.to_string(),
|
||||
icon.size.unwrap_or_else(|| renderer.default_size()),
|
||||
icon.font,
|
||||
);
|
||||
|
||||
let mut text_node = layout::Node::new(
|
||||
text_bounds - Size::new(icon_width + icon.spacing, 0.0),
|
||||
);
|
||||
|
||||
let mut icon_node =
|
||||
layout::Node::new(Size::new(icon_width, text_bounds.height));
|
||||
|
||||
match icon.side {
|
||||
Side::Left => {
|
||||
text_node.move_to(Point::new(
|
||||
padding.left + icon_width + icon.spacing,
|
||||
padding.top,
|
||||
));
|
||||
|
||||
icon_node.move_to(Point::new(padding.left, padding.top));
|
||||
}
|
||||
Side::Right => {
|
||||
text_node.move_to(Point::new(padding.left, padding.top));
|
||||
|
||||
icon_node.move_to(Point::new(
|
||||
padding.left + text_bounds.width - icon_width,
|
||||
padding.top,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
layout::Node::with_children(
|
||||
text_bounds.pad(padding),
|
||||
vec![text_node, icon_node],
|
||||
)
|
||||
} else {
|
||||
let mut text = layout::Node::new(text_bounds);
|
||||
text.move_to(Point::new(padding.left, padding.top));
|
||||
|
||||
layout::Node::with_children(text.size().pad(padding), vec![text])
|
||||
layout::Node::with_children(text_bounds.pad(padding), vec![text])
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
|
||||
|
|
@ -410,7 +516,7 @@ pub fn update<'a, Message, Renderer>(
|
|||
size: Option<f32>,
|
||||
font: Option<Renderer::Font>,
|
||||
is_secure: bool,
|
||||
on_change: &dyn Fn(String) -> Message,
|
||||
on_input: Option<&dyn Fn(String) -> Message>,
|
||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||
on_submit: &Option<Message>,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
|
|
@ -423,7 +529,8 @@ where
|
|||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
let state = state();
|
||||
let is_clicked = layout.bounds().contains(cursor_position);
|
||||
let is_clicked =
|
||||
layout.bounds().contains(cursor_position) && on_input.is_some();
|
||||
|
||||
state.is_focused = if is_clicked {
|
||||
state.is_focused.or_else(|| {
|
||||
|
|
@ -553,6 +660,8 @@ where
|
|||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
let Some(on_input) = on_input else { return event::Status::Ignored };
|
||||
|
||||
if state.is_pasting.is_none()
|
||||
&& !state.keyboard_modifiers.command()
|
||||
&& !c.is_control()
|
||||
|
|
@ -561,7 +670,7 @@ where
|
|||
|
||||
editor.insert(c);
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
|
||||
focus.updated_at = Instant::now();
|
||||
|
|
@ -574,6 +683,8 @@ where
|
|||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
let Some(on_input) = on_input else { return event::Status::Ignored };
|
||||
|
||||
let modifiers = state.keyboard_modifiers;
|
||||
focus.updated_at = Instant::now();
|
||||
|
||||
|
|
@ -599,7 +710,7 @@ where
|
|||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
editor.backspace();
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
}
|
||||
keyboard::KeyCode::Delete => {
|
||||
|
|
@ -619,7 +730,7 @@ where
|
|||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
editor.delete();
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
}
|
||||
keyboard::KeyCode::Left => {
|
||||
|
|
@ -694,7 +805,7 @@ where
|
|||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
editor.delete();
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
}
|
||||
keyboard::KeyCode::V => {
|
||||
|
|
@ -721,7 +832,7 @@ where
|
|||
let message = if let Some(paste) = &on_paste {
|
||||
(paste)(editor.contents())
|
||||
} else {
|
||||
(on_change)(editor.contents())
|
||||
(on_input)(editor.contents())
|
||||
};
|
||||
shell.publish(message);
|
||||
|
||||
|
|
@ -815,7 +926,9 @@ pub fn draw<Renderer>(
|
|||
placeholder: &str,
|
||||
size: Option<f32>,
|
||||
font: Option<Renderer::Font>,
|
||||
is_disabled: bool,
|
||||
is_secure: bool,
|
||||
icon: Option<&Icon<Renderer::Font>>,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
|
|
@ -825,11 +938,15 @@ pub fn draw<Renderer>(
|
|||
let value = secure_value.as_ref().unwrap_or(value);
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let text_bounds = layout.children().next().unwrap().bounds();
|
||||
|
||||
let mut children_layout = layout.children();
|
||||
let text_bounds = children_layout.next().unwrap().bounds();
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let appearance = if state.is_focused() {
|
||||
let appearance = if is_disabled {
|
||||
theme.disabled(style)
|
||||
} else if state.is_focused() {
|
||||
theme.focused(style)
|
||||
} else if is_mouse_over {
|
||||
theme.hovered(style)
|
||||
|
|
@ -847,6 +964,20 @@ pub fn draw<Renderer>(
|
|||
appearance.background,
|
||||
);
|
||||
|
||||
if let Some(icon) = icon {
|
||||
let icon_layout = children_layout.next().unwrap();
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: &icon.code_point.to_string(),
|
||||
size: icon.size.unwrap_or_else(|| renderer.default_size()),
|
||||
font: icon.font,
|
||||
color: appearance.icon_color,
|
||||
bounds: icon_layout.bounds(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
});
|
||||
}
|
||||
|
||||
let text = value.to_string();
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
|
@ -959,6 +1090,8 @@ pub fn draw<Renderer>(
|
|||
content: if text.is_empty() { placeholder } else { &text },
|
||||
color: if text.is_empty() {
|
||||
theme.placeholder_color(style)
|
||||
} else if is_disabled {
|
||||
theme.disabled_color(style)
|
||||
} else {
|
||||
theme.value_color(style)
|
||||
},
|
||||
|
|
@ -987,9 +1120,14 @@ pub fn draw<Renderer>(
|
|||
pub fn mouse_interaction(
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
is_disabled: bool,
|
||||
) -> mouse::Interaction {
|
||||
if layout.bounds().contains(cursor_position) {
|
||||
if is_disabled {
|
||||
mouse::Interaction::NotAllowed
|
||||
} else {
|
||||
mouse::Interaction::Text
|
||||
}
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ use crate::core::renderer;
|
|||
use crate::core::touch;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Background, Clipboard, Color, Element, Length, Pixels, Point, Rectangle,
|
||||
Shell, Size, Widget,
|
||||
Clipboard, Color, Element, Length, Pixels, Point, Rectangle, Shell, Size,
|
||||
Widget,
|
||||
};
|
||||
|
||||
/// An vertical bar and a handle that selects a single value from a range of
|
||||
|
|
@ -366,38 +366,6 @@ pub fn draw<T, R>(
|
|||
style_sheet.active(style)
|
||||
};
|
||||
|
||||
let rail_x = bounds.x + (bounds.width / 2.0).round();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - 1.0,
|
||||
y: bounds.y,
|
||||
width: 2.0,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail_colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x + 1.0,
|
||||
y: bounds.y,
|
||||
width: 2.0,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(style.rail_colors.1),
|
||||
);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) = match style
|
||||
.handle
|
||||
.shape
|
||||
|
|
@ -416,18 +384,50 @@ pub fn draw<T, R>(
|
|||
(start.into() as f32, end.into() as f32)
|
||||
};
|
||||
|
||||
let handle_offset = if range_start >= range_end {
|
||||
let offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.height - handle_width) * (value - range_end)
|
||||
(bounds.height - handle_width / 2.0) * (value - range_end)
|
||||
/ (range_start - range_end)
|
||||
};
|
||||
|
||||
let rail_x = bounds.x + bounds.width / 2.0;
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - (handle_height / 2.0),
|
||||
y: bounds.y + handle_offset.round(),
|
||||
x: rail_x - style.rail.width / 2.0,
|
||||
y: bounds.y,
|
||||
width: style.rail.width,
|
||||
height: offset + handle_width / 2.0,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.1,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - style.rail.width / 2.0,
|
||||
y: bounds.y + offset + handle_width / 2.0,
|
||||
width: style.rail.width,
|
||||
height: bounds.height - offset,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - handle_height / 2.0,
|
||||
y: bounds.y + offset,
|
||||
width: handle_height,
|
||||
height: handle_width,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_winit"
|
||||
version = "0.8.0"
|
||||
version = "0.9.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A winit runtime for Iced"
|
||||
|
|
@ -11,11 +11,16 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
|||
categories = ["gui"]
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
||||
trace = ["tracing", "tracing-core", "tracing-subscriber"]
|
||||
chrome-trace = ["trace", "tracing-chrome"]
|
||||
debug = ["iced_runtime/debug"]
|
||||
system = ["sysinfo"]
|
||||
application = []
|
||||
x11 = ["winit/x11"]
|
||||
wayland = ["winit/wayland"]
|
||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||
|
||||
[dependencies]
|
||||
window_clipboard = "0.2"
|
||||
|
|
@ -26,17 +31,18 @@ thiserror = "1.0"
|
|||
version = "0.27"
|
||||
git = "https://github.com/iced-rs/winit.git"
|
||||
rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c"
|
||||
default-features = false
|
||||
|
||||
[dependencies.iced_runtime]
|
||||
version = "0.9"
|
||||
version = "0.1"
|
||||
path = "../runtime"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../graphics"
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../style"
|
||||
|
||||
[dependencies.tracing]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t
|
|||
Add `iced_winit` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_winit = "0.8"
|
||||
iced_winit = "0.9"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
|
|
|
|||
|
|
@ -179,13 +179,17 @@ where
|
|||
.unwrap_or(None)
|
||||
});
|
||||
|
||||
let _ = match target {
|
||||
Some(node) => node
|
||||
.replace_child(&canvas, &node)
|
||||
.expect(&format!("Could not replace #{}", node.id())),
|
||||
None => body
|
||||
match target {
|
||||
Some(node) => {
|
||||
let _ = node
|
||||
.replace_with_with_node_1(&canvas)
|
||||
.expect(&format!("Could not replace #{}", node.id()));
|
||||
}
|
||||
None => {
|
||||
let _ = body
|
||||
.append_child(&canvas)
|
||||
.expect("Append canvas to HTML body"),
|
||||
.expect("Append canvas to HTML body");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -762,6 +766,9 @@ pub fn run_command<A, E>(
|
|||
mode,
|
||||
));
|
||||
}
|
||||
window::Action::ChangeIcon(icon) => {
|
||||
window.set_window_icon(conversion::icon(icon))
|
||||
}
|
||||
window::Action::FetchMode(tag) => {
|
||||
let mode = if window.is_visible().unwrap_or(true) {
|
||||
conversion::mode(window.fullscreen())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Convert [`winit`] types into [`iced_native`] types, and viceversa.
|
||||
//!
|
||||
//! [`winit`]: https://github.com/rust-windowing/winit
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
use crate::core::keyboard;
|
||||
use crate::core::mouse;
|
||||
use crate::core::touch;
|
||||
|
|
@ -219,7 +219,7 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
|
|||
/// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
pub fn mouse_interaction(
|
||||
interaction: mouse::Interaction,
|
||||
) -> winit::window::CursorIcon {
|
||||
|
|
@ -237,13 +237,14 @@ pub fn mouse_interaction(
|
|||
winit::window::CursorIcon::EwResize
|
||||
}
|
||||
Interaction::ResizingVertically => winit::window::CursorIcon::NsResize,
|
||||
Interaction::NotAllowed => winit::window::CursorIcon::NotAllowed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `MouseButton` from [`winit`] to an [`iced_native`] mouse button.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
|
||||
match mouse_button {
|
||||
winit::event::MouseButton::Left => mouse::Button::Left,
|
||||
|
|
@ -259,7 +260,7 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
|
|||
/// modifiers state.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
pub fn modifiers(
|
||||
modifiers: winit::event::ModifiersState,
|
||||
) -> keyboard::Modifiers {
|
||||
|
|
@ -286,7 +287,7 @@ pub fn cursor_position(
|
|||
/// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
pub fn touch_event(
|
||||
touch: winit::event::Touch,
|
||||
scale_factor: f64,
|
||||
|
|
@ -317,7 +318,7 @@ pub fn touch_event(
|
|||
/// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
pub fn key_code(
|
||||
virtual_keycode: winit::event::VirtualKeyCode,
|
||||
) -> keyboard::KeyCode {
|
||||
|
|
@ -510,6 +511,15 @@ pub fn user_attention(
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts some [`Icon`] into it's `winit` counterpart.
|
||||
///
|
||||
/// Returns `None` if there is an error during the conversion.
|
||||
pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
||||
let (pixels, size) = icon.into_raw();
|
||||
|
||||
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
|
||||
}
|
||||
|
||||
// As defined in: http://www.unicode.org/faq/private_use.html
|
||||
pub(crate) fn is_private_use_character(c: char) -> bool {
|
||||
matches!(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
//! Additionally, a [`conversion`] module is available for users that decide to
|
||||
//! implement a custom event loop.
|
||||
//!
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
|
||||
//! [`winit`]: https://github.com/rust-windowing/winit
|
||||
//! [`conversion`]: crate::conversion
|
||||
#![doc(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ mod platform;
|
|||
pub use platform::PlatformSpecific;
|
||||
|
||||
use crate::conversion;
|
||||
use crate::core::window::Icon;
|
||||
use crate::Position;
|
||||
|
||||
use winit::monitor::MonitorHandle;
|
||||
|
|
@ -84,7 +85,7 @@ pub struct Window {
|
|||
pub always_on_top: bool,
|
||||
|
||||
/// The window icon, which is also usually used in the taskbar
|
||||
pub icon: Option<winit::window::Icon>,
|
||||
pub icon: Option<Icon>,
|
||||
|
||||
/// Platform specific settings.
|
||||
pub platform_specific: platform::PlatformSpecific,
|
||||
|
|
@ -126,8 +127,9 @@ impl Window {
|
|||
.with_resizable(self.resizable)
|
||||
.with_decorations(self.decorations)
|
||||
.with_transparent(self.transparent)
|
||||
.with_window_icon(self.icon)
|
||||
.with_always_on_top(self.always_on_top);
|
||||
.with_window_icon(self.icon.and_then(conversion::icon))
|
||||
.with_always_on_top(self.always_on_top)
|
||||
.with_visible(self.visible);
|
||||
|
||||
if let Some(position) = conversion::position(
|
||||
primary_monitor.as_ref(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue