diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c296de4f53..76856358ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,10 @@ # How to contribute -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. +**IMPORTANT:** The Druid project is being discontinued. While we will still accept +all contributions, we'll prefer bugfixes and documentations improvements to new +features. + +If you still want to contribute, here are the guidelines you need to follow. ## Changelog @@ -102,7 +105,7 @@ adds Druid as a dependency and it won't even compile. For that reason our CI testing always uses the highest version that is still compatible. This mimics what a new developer would experience when they start using Druid. -What about the the minimum supported version or all the versions between the minimum and maximum? +What about the minimum supported version or all the versions between the minimum and maximum? It is not practical for us to test all the combinations of possible sub-dependency versions. Without testing there can easily be mistakes. Let's say our `Cargo.toml` specifies that we depend on the package `foo` version `^1.1.1` and the latest `foo` version is `1.1.3`. diff --git a/README.md b/README.md index 837f1ed405..ef714dc199 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ performance, a rich palette of interactions (hence a widget library to support them), and playing well with the native platform. See the [goals section](#Goals) for more details. -Druid's current development is largely driven by its use in [Runebender], a new -font editor. +**IMPORTANT:** The Druid project is being discontinued. While we will still accept +all contributions, we'll prefer bugfixes and documentations improvements to new +features. Current development effort is focused on [Xilem](https://github.com/linebender/xilem). -We have been doing periodic releases of Druid on crates.io, but it is under -active development and its API might change. All changes are documented +We have been doing periodic releases of Druid on crates.io, but the API is still unstable. All changes are documented in [the changelog](https://github.com/linebender/druid/blob/master/CHANGELOG.md). For an overview of some key concepts, see the (work in progress) [Druid book]. @@ -130,7 +130,7 @@ the Rust community is working on a variety of different libraries with different goals, so here are some of Druid's non-goals and possible alternatives that can offer those capabilities: -- Use the the platform-native widgets or mimic them. ([Relm], [Slint]) +- Use the platform-native widgets or mimic them. ([Relm], [Slint]) - Embed easily into custom render pipelines. ([Conrod]) - Adhere to a specific architectural style such as Elm. ([Iced], [Relm]) - Support rendering to HTML when targeting the web. ([Iced], [Moxie]) diff --git a/docs/book_examples/Cargo.toml b/docs/book_examples/Cargo.toml index 7027d55f87..195df7650d 100644 --- a/docs/book_examples/Cargo.toml +++ b/docs/book_examples/Cargo.toml @@ -5,4 +5,5 @@ authors = ["Colin Rofls "] edition = "2018" [dependencies] -druid = { path = "../../druid" } +druid = { path = "../../druid", features = [ "im" ] } +im = { version = "15.0.0" } diff --git a/docs/book_examples/src/getting_started_2_md.rs b/docs/book_examples/src/getting_started_2_md.rs new file mode 100644 index 0000000000..e8f00a1f6a --- /dev/null +++ b/docs/book_examples/src/getting_started_2_md.rs @@ -0,0 +1,126 @@ +#![allow(unused)] + +// ANCHOR: example_1_imports +use druid::widget::Label; +use druid::{AppLauncher, Widget, WindowDesc}; +// ANCHOR_END: example_1_imports + +// ANCHOR: example_2_imports +use druid::widget::{Container, Flex, Split}; +use druid::Color; +// ANCHOR_END: example_2_imports + +// ANCHOR: example_3_imports +use im::Vector; +// ANCHOR_END: example_3_imports + +// ANCHOR: example_3b_imports +use druid::widget::List; +// ANCHOR_END: example_3b_imports + +// ANCHOR: example_4_imports +use im::vector; +// ANCHOR_END: example_4_imports + +// ANCHOR: example_5_imports +use druid::widget::Button; +// ANCHOR_END: example_5_imports + +// --- + +// ANCHOR: example_6_imports +use druid::{Data, Lens}; +// ANCHOR_END: example_6_imports + +// ANCHOR: example_6_derive +#[derive(Clone, Data, Lens)] +// ANCHOR_END: example_6_derive + +// ANCHOR: example_6_struct +struct TodoList { + items: Vector, + next_item: String, +} +// ANCHOR_END: example_6_struct + +// ANCHOR: example_7_imports +use druid::widget::LensWrap; +// ANCHOR_END: example_7_imports + +fn build_example_7() -> impl Widget { + // ANCHOR: example_7 + // Replace previous List with: + LensWrap::new( + List::new(|| Label::dynamic(|data, _| format!("List item: {}", data))), + TodoList::items, + ) + // ANCHOR_END: example_7 +} + +fn build_example_7b() -> impl Widget { + // ANCHOR: example_7b + // Replace previous Button with: + Button::new("Add item").on_click(|_, data: &mut TodoList, _| { + data.items.push_back(data.next_item.clone()); + data.next_item = String::new(); + }) + // ANCHOR_END: example_7b +} + +// ANCHOR: example_8_imports +use druid::widget::TextBox; +// ANCHOR_END: example_8_imports + +fn build_example_8() -> impl Widget { + // ANCHOR: example_8 + // Replace `Label::new("Textbox placeholder")` with + LensWrap::new(TextBox::new(), TodoList::next_item) + // ANCHOR_END: example_8 +} + +// ANCHOR: complete_code +fn build_ui() -> impl Widget { + Split::columns( + Container::new( + // Dynamic list of Widgets + LensWrap::new( + List::new(|| Label::dynamic(|data, _| format!("List item: {}", data))), + TodoList::items, + ), + ) + .border(Color::grey(0.6), 2.0), + Container::new( + Flex::column() + .with_flex_child( + Button::new("Add item").on_click(|_, data: &mut TodoList, _| { + data.items.push_back(data.next_item.clone()); + data.next_item = String::new(); + }), + 1.0, + ) + .with_flex_child(LensWrap::new(TextBox::new(), TodoList::next_item), 1.0), + ) + .border(Color::grey(0.6), 2.0), + ) +} + +fn main() { + let main_window = WindowDesc::new(build_ui()) + .window_size((600.0, 400.0)) + .title("My first Druid App"); + let initial_data = TodoList { + items: vector![ + "first item".into(), + "second item".into(), + "third item".into(), + "foo".into(), + "bar".into(), + ], + next_item: String::new(), + }; + + AppLauncher::with_window(main_window) + .launch(initial_data) + .expect("Failed to launch application"); +} +// ANCHOR_END: complete_code diff --git a/docs/book_examples/src/getting_started_md.rs b/docs/book_examples/src/getting_started_md.rs new file mode 100644 index 0000000000..8649ad0e2b --- /dev/null +++ b/docs/book_examples/src/getting_started_md.rs @@ -0,0 +1,122 @@ +#![allow(clippy::let_unit_value)] + +// ANCHOR: example_1_imports +use druid::widget::Label; +use druid::{AppLauncher, Widget, WindowDesc}; +// ANCHOR_END: example_1_imports + +// ANCHOR: example_2_imports +use druid::widget::{Container, Flex, Split}; +use druid::Color; +// ANCHOR_END: example_2_imports + +// ANCHOR: example_3_imports +use im::Vector; +// ANCHOR_END: example_3_imports + +// ANCHOR: example_3b_imports +use druid::widget::List; +// ANCHOR_END: example_3b_imports + +// ANCHOR: example_4_imports +use im::vector; +// ANCHOR_END: example_4_imports + +// ANCHOR: example_5_imports +use druid::widget::Button; +// ANCHOR_END: example_5_imports + +// ANCHOR: example_1 +fn build_ui() -> impl Widget<()> { + Label::new("Hello world") +} + +fn main() { + let main_window = WindowDesc::new(build_ui()) + .window_size((600.0, 400.0)) + .title("My first Druid App"); + let initial_data = (); + + AppLauncher::with_window(main_window) + .launch(initial_data) + .expect("Failed to launch application"); +} +// ANCHOR_END: example_1 + +fn build_example_2() -> impl Widget<()> { + // ANCHOR: example_2_builder + Split::columns( + Container::new( + Flex::column() + .with_flex_child(Label::new("first item"), 1.0) + .with_flex_child(Label::new("second item"), 1.0) + .with_flex_child(Label::new("third item"), 1.0) + .with_flex_child(Label::new("fourth item"), 1.0), + ) + .border(Color::grey(0.6), 2.0), + Container::new( + Flex::column() + .with_flex_child(Label::new("Button placeholder"), 1.0) + .with_flex_child(Label::new("Textbox placeholder"), 1.0), + ) + .border(Color::grey(0.6), 2.0), + ) + // ANCHOR_END: example_2_builder +} + +type TodoList = Vector; + +fn build_example_3() -> impl Widget { + // ANCHOR: example_3_builder + Split::columns( + Container::new( + // Dynamic list of Widgets + List::new(|| Label::dynamic(|data, _| format!("List item: {}", data))), + ) + .border(Color::grey(0.6), 2.0), + Container::new( + Flex::column() + .with_flex_child(Label::new("Button placeholder"), 1.0) + .with_flex_child(Label::new("Textbox placeholder"), 1.0), + ) + .border(Color::grey(0.6), 2.0), + ) + // ANCHOR_END: example_3_builder +} + +fn example_4_main() { + fn build_ui() -> impl Widget { + build_example_3() + } + + // ANCHOR: example_4_main + let main_window = WindowDesc::new(build_ui()) + .window_size((600.0, 400.0)) + .title("My first Druid App"); + let initial_data = vector![ + "first item".into(), + "second item".into(), + "third item".into(), + "foo".into(), + "bar".into(), + ]; + + AppLauncher::with_window(main_window) + .launch(initial_data) + .expect("Failed to launch application"); + // ANCHOR_END: example_4_main +} + +fn build_example_5a() -> impl Widget { + // ANCHOR: example_5a_button + // Replace `Label::new("Button placeholder")` with + Button::new("Add item") + // ANCHOR_END: example_5a_button +} + +fn build_example_5b() -> impl Widget { + // ANCHOR: example_5b_button + Button::new("Add item") + .on_click(|_, data: &mut Vector, _| data.push_back("New item".into())) + // ANCHOR_END: example_5b_button +} diff --git a/docs/book_examples/src/lib.rs b/docs/book_examples/src/lib.rs index a4fd098b56..ebc3fb8f8e 100644 --- a/docs/book_examples/src/lib.rs +++ b/docs/book_examples/src/lib.rs @@ -5,5 +5,7 @@ mod custom_widgets_md; mod data_md; mod env_md; +mod getting_started_2_md; +mod getting_started_md; mod lens_md; mod widget_md; diff --git a/docs/src/intro.md b/docs/src/01_overview.md similarity index 73% rename from docs/src/intro.md rename to docs/src/01_overview.md index 5062d5ad82..d1314141f5 100644 --- a/docs/src/intro.md +++ b/docs/src/01_overview.md @@ -1,5 +1,7 @@ # Druid +**Note:** Druid is being discontinued in favor of other projects based on the same general principles, such as [Xilem](https://github.com/linebender/xilem/). + Druid is a framework for building simple graphical applications. Druid is composed of a number of related projects. [`druid-shell`] is a @@ -8,17 +10,17 @@ current OS & window manager. [`piet`] is an abstraction for doing 2D graphics; [`kurbo`] is a library for 2D geometry; and [`druid`] itself is an opinionated set of high-level APIs for building cross-platform desktop applications. -Druid is *data oriented*. It shares many ideas (and is directly inspired by) +The framework is *data oriented*. It shares many ideas (and is directly inspired by) contemporary declarative UI frameworks such as [Flutter], [Jetpack Compose], and [SwiftUI], while also attempting to be conceptually simple and largely *non-magical*. A programmer familiar with Rust should be able to understand how Druid works without special difficulty. -## Goals and Status +## Prerequisites -The current goal of Druid is to make it easy to write a program in Rust that -can present a GUI and accept user input. Running your program should be as -simple as `cargo run`. +This tutorial assumes basic familliarity with Rust and a working setup with the basic tooling like +Rustup and Cargo. This tutorial will use stable Rust (v1.65.0 at the time of writing) and the latest +released version of Druid (v0.8). ## Key Concepts diff --git a/docs/src/02_getting_started.md b/docs/src/02_getting_started.md new file mode 100644 index 0000000000..a64e467dc5 --- /dev/null +++ b/docs/src/02_getting_started.md @@ -0,0 +1,266 @@ +# Get started with Druid + +This chapter will walk you through setting up a simple Druid application from start to finish. + + +## Setting up Druid dependencies + +If you're on Linux or OpenBSD, you'll need to install GTK-3's development kit first. + +### Linux +On Linux, Druid requires gtk+3. + +On Ubuntu this can be installed with +```sh +> sudo apt-get install libgtk-3-dev +``` + +On Fedora +```sh +> sudo dnf install gtk3-devel glib2-devel +``` + +See [GTK installation page] for more installation instructions. + +### OpenBSD +On OpenBSD, Druid requires gtk+3; install from packages: +```sh +> pkg_add gtk+3 +``` + + +## Starting a project + +Create a new cargo binary crate, and add druid as a dependency: + +```sh +> cargo new my-druid-app + Created binary (application) `my-druid-app` package +> cd my-druid-app +> cargo add druid +``` + +You should now have a stub of a project: + +```sh +> tree +. +├── Cargo.lock +├── Cargo.toml +└── src + └── main.rs +``` + +## Hello world + +To show a minimal window with a label, write the following code in your `main.rs`: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_1_imports}} + +{{#include ../book_examples/src/getting_started_md.rs:example_1}} +``` + +In our main function we create an `AppLauncher`, pass it a `WindowDesc`, and launch it. We use `build_ui` to create a tree of widgets to pass to our `WindowDesc`. For now this tree consists of one simple label widget. + +This is a very simple example application, using only the bare minimum of features. We can do something more complex. + + +## Add more widgets + +The first thing we could do to make our example application more interesting is to display more than one widget. However, `WindowDesc::new` expects a function that returns only one Widget. We also need a way to tell Druid how to lay out our widgets. + +What we need to do is initialize our `WindowDesc` with a widget tree, with a single widget at the root. Some widgets can have children, and know how to lay them out; these are called container widgets. + +We describe our window as a widget tree with container widgets as nodes, and label widgets as the leaves. Our `build_ui` function is then responsible for building this widget tree. + +As an example, we'll build a todo-list app. At first, this app will have two columns, one with the list, and one with a placeholder for a button, each in a box with visible borders. We'll need to use the `Split`, `Flex` and `Container` widgets: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_2_imports}} + +// ... + +fn build_ui() -> impl Widget<()> { +{{#include ../book_examples/src/getting_started_md.rs:example_2_builder}} +} +``` + +We get a UI which is starting to look like a real application. Still, it's inherently static. We would like to add some interactivity, but before we can do that, our next step will be to make the UI data-driven. + + +## Widget data + +You may have noticed that our `build_ui()` function returns `impl Widget<()>`. This syntax describes an existential type which implements the `Widget` trait, with a generic parameter. + +This generic parameter is the Widget's data. Since our UI so far has been stateless, the data is the unit type. But since we're writing a todo-list, we'll want our widget to depend on the list data: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_3_imports}} +type TodoList = Vector; + +// ... + +fn build_ui() -> impl Widget { + // ... +} +``` + +Here we're using a Vector from the `im` crate; for reasons we'll get into later, we can't use the standard library's Vec as our data. But `im::Vector` is functionally equivalent to `std::vec::Vec`. + +To build a UI that changes depending on our widget data, we use the `List` widget, and `Label::dynamic`: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_3b_imports}} + +// ... + +fn build_ui() -> impl Widget { +{{#include ../book_examples/src/getting_started_md.rs:example_3_builder}} +} +``` + +List is a special widget that takes a collection as data, and creates one widget with per collection item, with the item as data. In other words, our `List` implements `Widget>` while the label returned by `Label::dynamic` implements `Widget`. This is all resolved automatically by type inference. + +`Label::dynamic` creates a label whose content depends on the data parameter. + +Now, to test our UI, we can launch it with a hardcoded list: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_4_imports}} + +// ... + +fn main() { +{{#include ../book_examples/src/getting_started_md.rs:example_4_main}} +} +``` + +We can now change the contents of the UI depending on the data we want to display; but our UI is still static. To add user interaction, we need a way to modify our data. + + +## Interaction widgets + +First, to interact with our UI, we add a button: + + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_5_imports}} + +// ... + +fn build_ui() -> impl Widget { + // ... + +{{#include ../book_examples/src/getting_started_md.rs:example_5a_button}} + + // ... +} +``` + +If you build this, you'll notice clicking the button doesn't do anything. We need to give it a callback, that will take the data as parameter and mutate it: + +```rust, noplaypen +fn build_ui() -> impl Widget { + // ... + +{{#include ../book_examples/src/getting_started_md.rs:example_5b_button}} + + // ... +} +``` + +Now, clicking on the button adds an item to our list, but it always adds the same item. To change this, we need to add a textbox to our app, which will require that we make our data type a bit more complex. + + +### Selecting a structure's field with lenses + +To complete our todo-list, we need to change our app data type. Instead of just having a list of strings, we need to have a list *and* a string representing the next item to be added: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_2_md.rs:example_6_struct}} +``` + +However, now we have a problem: our List widget which expected a `Vector<...>` won't know how to handle a struct. So, we need to modify Druid's dataflow so that, given the TodoList above, the List widget will have access to the `items` field. This is done with a `Lens`, which we'll explain next chapter. + +Furthermore, to pass our type as the a generic parameter to `Widget`, we need it to implement the `Data` trait (and `Clone`), more on that next chapter. + +So, given the two requirements above, our declaration will actually look like: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_2_md.rs:example_6_imports}} + +{{#include ../book_examples/src/getting_started_2_md.rs:example_6_derive}} +{{#include ../book_examples/src/getting_started_2_md.rs:example_6_struct}} +``` + +Among other things, the above declaration defines two lenses, `TodoList::items` and `TodoList::next_item`, which take a TodoList as input and give a mutable reference to its `items` and `next_item` fields, respectively. + +Next, we'll use the `LensWrap` widget wrapper to pass `items` to our `List` widget: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_2_md.rs:example_7_imports}} + +// ... + +fn build_ui() -> impl Widget { + // ... + +{{#include ../book_examples/src/getting_started_2_md.rs:example_7}} + + // ... +} +``` + +We also need to modify the callback of our button: + +```rust, noplaypen +fn build_ui() -> impl Widget { + // ... + +{{#include ../book_examples/src/getting_started_2_md.rs:example_7b}} + + // ... +} +``` + +Finally, we add a textbox to our widget with `TodoList::next_item` as its data: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_2_md.rs:example_8_imports}} + +// ... + +fn build_ui() -> impl Widget { + // ... + +{{#include ../book_examples/src/getting_started_2_md.rs:example_8}} + + // ... +} +``` + +Now, when we push the button, whatever was in the textbox is added to the list. + + +## Putting it all together + +If we pull all the code we have written so far, our `main.rs` now looks like this: + +```rust, noplaypen +{{#include ../book_examples/src/getting_started_md.rs:example_1_imports}} +{{#include ../book_examples/src/getting_started_md.rs:example_2_imports}} +{{#include ../book_examples/src/getting_started_md.rs:example_3b_imports}} +{{#include ../book_examples/src/getting_started_md.rs:example_3_imports}} +{{#include ../book_examples/src/getting_started_md.rs:example_4_imports}} +{{#include ../book_examples/src/getting_started_md.rs:example_5_imports}} +{{#include ../book_examples/src/getting_started_2_md.rs:example_6_imports}} +{{#include ../book_examples/src/getting_started_2_md.rs:example_7_imports}} +{{#include ../book_examples/src/getting_started_2_md.rs:example_8_imports}} + +{{#include ../book_examples/src/getting_started_2_md.rs:complete_code}} +``` + +We now have a list of items, which we can add to by filling a textbox and clicking a button. + +[GTK installation page]: https://www.gtk.org/docs/installations/linux/ diff --git a/docs/src/03_data.md b/docs/src/03_data.md new file mode 100644 index 0000000000..d81f0e3c29 --- /dev/null +++ b/docs/src/03_data.md @@ -0,0 +1,97 @@ +# Dataflow and the `Data` trait + +The Druid architecture is based on a two-way dataflow. + +At the root level, you define the application state, which is passed to each child widget as associated data. Some Widgets (eg LensWrap) will only pass a subset of that data to their children. + +Some widgets (eg Button, TextBox, Checkbox) can mutate the data passed to them by their parents in reaction to user events. The data mutated in a child widget is also changed in the parent widgets, all the way to the root. + +When you mutate a widget's associated data, Druid compares the old and new version, and propagates the change to the widgets that are affected by the change. + +Note that, in all that workflow, Widgets don't actually store their associated data. A `Button>` doesn't actually store a `Vector`, instead the framework stores one per button, which is provided to widget methods. + +For this to work, your model must implement the `Clone` and `Data` traits. The `Data` trait has a single method: + +```rust,no_run,noplaypen +{{#include ../../druid/src/data.rs:same_fn}} +``` + +This method checks for equality, but allows for false negatives. + + +## Performance + +It is important that your data is cheap to clone and cheap to compare; we encourage the use of reference counted pointers to allow cheap cloning of more expensive types. `Arc` and `Rc` have blanket `Data` impls that do pointer comparison, so if you have a type that does not implement `Data`, you can always just wrap it in one of those smart pointers. + +### Collections + +`Data` is expected to be cheap to clone and cheap to compare, which can cause +issues with collection types. For this reason, `Data` is not implemented for +`std` types like `Vec` or `HashMap`. + +You can always put these types inside an `Rc` or an `Arc`, or if you're dealing with +larger collections you can build Druid with the `im` feature, which brings in +the [`im` crate], and adds a `Data` impl for the collections there. The [`im` +crate] is a collection of immutable data structures that act a lot like the `std` +collections, but can be cloned efficiently. + + +## Derive + +`Data` can be derived. This is recursive; it requires `Data` to be implemented +for all members. For 'C style' enums (enums where no variant has any fields) +this also requires an implementation of `PartialEq`. `Data` is implemented for +a number of `std` types, including all primitive types, `String`, `Arc`, `Rc`, +as well as `Option`, `Result`, and various tuples whose members implement +`Data`. + +Here is an example of using `Data` to implement a simple data model: + +```rust +{{#include ../book_examples/src/data_md.rs:derive}} +``` + +[`im` crate]: https://docs.rs/im + + +## Mapping `Data` with lenses + +In Druid, most container widgets expect their children to have the same associated data. If you have a `Flex`, you can only append widgets that implement `Widget` to it. + +In some cases, however, you want to compose widgets that operate on different subsets of the data. Maybe you want to add two widgets to the above Flex, one that uses the field `foo` and another that uses the field `bar`, and they might respectively implement `Widget` and `Widget`. + +Lenses allow you to bridge that type difference. A lens is a type that represents a *two-way* mapping between two data types. That is, a lens from X to Y can take an instance of X and give you an instance of Y, and can take a modified Y and apply the modification to X. + +To expand on our Foobar example: + +```rust +#[derive(Lens)] +struct Foobar { + foo: Foo, + bar: Bar, +} +``` + +The derive macro above generates two lenses: `Foobar::foo` and `Foobar::bar`. `Foobar::foo` can take an instance of `Foobar` and give you a shared or mutable reference to its `foo` field. Finally, the type `LensWrap` can take that lens and use it to map between different widget types: + +```rust +fn build_foo() -> impl Widget { + // ... +} + +fn build_bar() -> impl Widget { + +} + +fn build_foobar() -> impl Widget { + Flex::column() + .with_child( + LensWrap::new(build_foo(), Foobar::foo), + ) + .with_child( + LensWrap::new(build_bar(), Foobar::bar), + ) +} +``` + +See the Lens chapter for a more in-depth explanation of what lenses are and how they're implemented. diff --git a/docs/src/widget.md b/docs/src/04_widget.md similarity index 100% rename from docs/src/widget.md rename to docs/src/04_widget.md diff --git a/docs/src/lens.md b/docs/src/05_lens.md similarity index 64% rename from docs/src/lens.md rename to docs/src/05_lens.md index 575c450df3..1d31732955 100644 --- a/docs/src/lens.md +++ b/docs/src/05_lens.md @@ -1,14 +1,13 @@ # Lenses and the `Lens` trait -One of the key abstractions in `druid` along with `Data` is the `Lens` trait. This page explains what they are, and then how to use them. `Lens`es may seem complicated at first, but they are also very powerful, allowing you to write code that is reusable, concise, and understandable (once you understand `Lens`es themselves). +One of the key abstractions in `druid` along with `Data` is the `Lens` trait. This page explains what they are, and then how to use them. `Lens`es are a complex but powerful concept, that allow you to abstract over the notion of "X owns an instance of Y". -## Fundamentals: Definition and Implementation -Like Rust itself, lenses are one of those things that require effort up front to learn, but are very fun and effective to use once you understand them. This section represents the effort part of the equation. I promise if you stick with it you will reap the rewards. +## Fundamentals: Definition and Implementation ### Definition -Let's start with the definition of a `Lens`: +Let's start with the (simplified) definition of a `Lens`: ```rust pub trait Lens { @@ -18,13 +17,11 @@ pub trait Lens { } ``` -I've copied this definition from the `druid` source code, but then simplified it a little, by removing the return types, as they are not fundamental to the way lenses work. - -The first thing to notice is the generics on the `Lens` itself. There are 3 types involve in the lens: the lens itself, `T` and `U`. The two type parameters represent the mismatch that lenses solve: we have a function that operates on `U`, and an object of type `T`, so we need to transform `T` into `U` somehow. +The first thing to notice is the generics on the `Lens` itself. There are 3 types involved in the lens: `Self` (the lens itself), `T` and `U`. The two type parameters represent the mismatch that lenses solve: we have a function that operates on `U`, and an object of type `T`, so we need to transform `T` into `U` somehow. ### Implementation -Time for an example. Let's implement & use `Lens` manually so we can see what's going on. +As an example, let's write a manual implementation of the `Lens` trait: ```rust struct Container { @@ -32,8 +29,8 @@ struct Container { another: String, } -// Here the lens doesn't have any data, but there are cases where -// it might, for example it might contain an index into a collection. +// This lens doesn't have any data, because it will always map to the same field. +// A lens that mapped to, say, an index in a collection, would need to store that index. struct InnerLens; // Our lens will apply functions that operate on a `String` to a `Container`. @@ -48,11 +45,9 @@ impl Lens for InnerLens { } ``` -This is a very simple case. All we need to do is project the function onto the field. Notice that this isn't the only valid lens from `Container` to `String` we could have made - we could also project from `Container` to `another`. We made the choice how to transform `Container` into `String` when we implemented `Lens`. - -> Side note: Actually we could project on to any string we have access to, including something in a global mutex, or a string that we create and discard in the lens. Lenses made like this are usually not what you want. +The implementation is straightforward: it projects the given function onto the `inner` field of our struct. (Notice that this isn't the only valid lens from `Container` to `String` we could have made - we could also project from `Container` to `another`). -You'll also notice that both methods take an immutable reference to `self`, even the `mut` variant. The lense itself should be thought of as a fixed thing that knows how to do the mapping. In the above case it contains no data, and will most likely not even be present in the final compiled/optimized code. +You'll also notice that both methods take an immutable reference to `self`, even the `mut` variant. The lense itself should be thought of as a fixed value that knows how to do the mapping. In the above case it contains no data, and will likely not even be present in the final compiled/optimized code. Now for a slightly more involved example @@ -137,23 +132,10 @@ impl Lens for NameLens { } ``` -Now in addition to almost free `Clone`s, we also have cheap incremental updates to the data itself. In the case of names, this isn't that important, but if the vector had `1_000_000_000` elements, we could still make changes in only *O(log(n))* time (in this case the difference between `1_000_000_000` and `30` - pretty big!). +Now in addition to almost free `Clone`s, we also have cheap incremental updates to the data itself. That means your UI won't get slowdowns if your data structure gets very large (eg a list of entries in a database). -Right, now you understand how `Lens`es work. Congratulations, you've done the hardest bit! If you get lost later on, read this section again, and eventually it will all make sense. +Hopefully, this makes sense to you. This was a technical overview of lenses as generic data structures. The next section will cover how lenses are integrated in Druid in more detail. -### Bonus - The actual `Lens` definition - -The actual definition of `Lens` in `druid` allows the user to return values from the lens. This isn't necessary for the core functioning of the lens, but it is useful. Also, because the types `T` and `U` always appear behind pointers (`&` and `&mut`), we can relax the `Sized` requirement that is applied by default, meaning we can implement `Lens` for types like `[T]` (slice) and `str`. - -Here is the real definition for completeness: - -```rust -pub trait Lens { - fn with V>(&self, data: &T, f: F) -> V; - - fn with_mut V>(&self, data: &mut T, f: F) -> V; -} -``` ## Lenses in Druid @@ -214,38 +196,16 @@ struct Inner { let composed_lens = Outer::inner.then(Inner::text); ``` -> Side note: Because unlike Haskell, Rust has all the type information during monomorphisation, it can inline all these functions and the extra cost of having 2 lens functions disappears, leaving what you would have written by hand. When you're waiting for Rust's infamously long compilations to finish, know that you're waiting for a potentially significant run-time benefit in exchange. - `LensExt` contains a few more useful methods for handling things like negating a boolean, or auto-`Deref`ing a value. There are also 3 special structs in `druid::lens`: `Constant`, `Identity` and `Unit`. `Constant` is a lens that always returns the same value, and always discards any changes, while `Identity` is a lens that does nothing. You might say "what is the point of a lens that does nothing", which would be a fair question. Well, there are some places where a lens is required, and having an identity allows the user to say act as if there was no lens. It's also used to begin a composition chain using the combinators like `then`. `Unit` is a special case of `Constant` where the constant in question is `()`. -> Side note: Because `()` only has 1 value, it does actually respect mutations. It's just that mutations always result in the same value again (`()`). - ### The `lens` macro Finally, there is a macro for constructing lenses on the fly. It allows you to lens into fields of a struct you don't control (so you can't derive `Lens` for it), it also allows lensing into tuples and tuple structs, and lastly it will create index lenses into slices. ### Wrapping up -Whew, that was quite complicated. Hopefully now you have a solid understanding of the problem that lenses solve, how they solve it, and how to use them effectively. Now you have the ability to relate your application data to widget data, allowing you to use reusable widgets in any configuration you want. If any parts of this page are confusing, please open an issue on the issue tracker or mention it on zulip, and we will see if we can improve the docs (and clear up any misunderstandings you might have). - -### Bonus - mapping between complex structs automatically - -Way back in the first section, we discussed lenses between the following: - -```rust -#[derive(Lens)] -struct Container { - first_name: Rc, - last_name: Rc, - age: u16, -} - -struct Name { - first: Rc, - last: Rc, -} -``` +Whew, that was quite complicated. Hopefully now you have a solid understanding of the problem that lenses solve, how they solve it, and how to use them effectively. -We showed that you can construct a lens from `Container` to `Name`, but it was a bit involved and required knowledge of the inner workings of `Lens`, something you probably don't want to think about. The crate `druid-lens-compose` is an experiment to allow for building a lens to a struct out of lenses to its fields. It's not well documented, but usage is fairly simple: derive the macro for the inner struct you want to lens to, run `cargo doc`, and look at the signature of the generated method/build struct. We'd be interested to hear if you found it useful, so please drop by the zulip and let us know! +If any parts of this page are confusing, please open an issue on the issue tracker or mention it on zulip, and we will see if we can improve the docs (and clear up any misunderstandings you might have). diff --git a/docs/src/env.md b/docs/src/06_env.md similarity index 65% rename from docs/src/env.md rename to docs/src/06_env.md index 1429603814..a521f020d7 100644 --- a/docs/src/env.md +++ b/docs/src/06_env.md @@ -24,12 +24,7 @@ The most prominent role of `Env` is to store a set of typed keys and values. The The only way to get an item out of the `Env` is with a [`Key`]. A [`Key`] is a combination of a string identifier and a type. -You can think of this as strict types, enforced at runtime. This is less scary -than it sounds, assuming the user follows a few simple guidelines. That said, **It is the -programmer's responsibility to ensure that the environment is used correctly**. -The API is aggressive about checking for misuse, and many methods will panic if -anything is amiss. In practice this should be easy to avoid, by following a few -simple guidelines. +You can think of this as strict types, enforced at runtime. **It is the programmer's responsibility to ensure that the key used to get a value has the same type as the one used to set it**. The API is aggressive about checking for misuse, and many methods will panic if anything is amiss. In practice this shouldn't almost never happen if you follow these guidelines: 1. **`Key`s should be `const`s with unique names.** If you need to use a custom key, you should declare it as a `const`, and give it a unique name. By @@ -40,12 +35,13 @@ simple guidelines. const BAD_NAME: Key = Key::new("height"); const GOOD_NAME: Key = Key::new("com.example.my-app.main-view-height"); ``` -1. **`Key`s must always be set before they are used.** In practice this means + +2. **`Key`s must always be set before they are used.** In practice this means that most keys are set when your application launches, using [`AppLauncher::configure_env`]. Once a key has been added to the `Env`, it cannot be deleted, although it can be overwritten. -1. **Values can only be overwritten by values of the same type.** If you have a +3. **Values can only be overwritten by values of the same type.** If you have a `Key`, assuming that key has already been added to the `Env`, you cannot replace it with any other type. @@ -74,25 +70,22 @@ the [`env_scope`] method on [`WidgetExt`]: ## Localization -*localization is currently half-baked* +*Localization is currently half-baked.* The `Env` contains the localization resources for the current locale. A [`LocalizedString`] can be resolved to a given string in the current locale by calling its [`resolve`] method. -In general, you should not need to worry about localization directly. See the -[localization] chapter for an overview of localization in Druid. - -[`Env`]: https://docs.rs/druid/0.7.0/druid/struct.Env.html -[`Key`]: https://docs.rs/druid/0.7.0/druid/struct.Key.html -[`Value`]: https://docs.rs/druid/0.7.0/druid/struct.Value.html -[`LocalizedString`]: https://docs.rs/druid/0.7.0/druid/struct.LocalizedString.html -[`resolve`]: https://docs.rs/druid/0.7.0/druid/struct.LocalizedString.html#method.resolve +[`Env`]: https://docs.rs/druid/latest/druid/struct.Env.html +[`Key`]: https://docs.rs/druid/latest/druid/struct.Key.html +[`Value`]: https://docs.rs/druid/latest/druid/enum.Value.html +[`LocalizedString`]: https://docs.rs/druid/latest/druid/struct.LocalizedString.html +[`resolve`]: https://docs.rs/druid/latest/druid/struct.LocalizedString.html#method.resolve [localization]: ./localization.md [reverse-DNS]: https://en.wikipedia.org/wiki/Reverse_domain_name_notation -[`AppLauncher::configure_env`]: https://docs.rs/druid/0.7.0/druid/struct.AppLauncher.html#method.configure_env -[`KeyOrValue`]: https://docs.rs/druid/0.7.0/druid/enum.KeyOrValue.html -[`EnvScope`]: https://docs.rs/druid/0.7.0/druid/widget/struct.EnvScope.html -[`WidgetExt`]: https://docs.rs/druid/0.7.0/druid/trait.WidgetExt.html -[`env_scope`]: https://docs.rs/druid/0.7.0/druid/trait.WidgetExt.html#method.env_scope +[`AppLauncher::configure_env`]: https://docs.rs/druid/latest/druid/struct.AppLauncher.html#method.configure_env +[`KeyOrValue`]: https://docs.rs/druid/latest/druid/enum.KeyOrValue.html +[`EnvScope`]: https://docs.rs/druid/latest/druid/widget/struct.EnvScope.html +[`WidgetExt`]: https://docs.rs/druid/latest/druid/trait.WidgetExt.html +[`env_scope`]: https://docs.rs/druid/latest/druid/trait.WidgetExt.html#method.env_scope diff --git a/docs/src/resolution_independence.md b/docs/src/07_resolution_independence.md similarity index 96% rename from docs/src/resolution_independence.md rename to docs/src/07_resolution_independence.md index 8591470f89..15b344b89e 100644 --- a/docs/src/resolution_independence.md +++ b/docs/src/07_resolution_independence.md @@ -68,7 +68,3 @@ Remember there might be multiple displays connected with different pixel densiti and your application might have multiple windows - with each window on a different display. It will all just work, because druid will adjust the actual pixel dimensions based on the display that the window is currently located on. - -## High pixel density images with druid - -*TODO: Write this section after it's more clear how this works and if its even solved.* \ No newline at end of file diff --git a/docs/src/custom_widgets.md b/docs/src/08_widgets_in_depth.md similarity index 91% rename from docs/src/custom_widgets.md rename to docs/src/08_widgets_in_depth.md index 806611288c..313cb654a7 100644 --- a/docs/src/custom_widgets.md +++ b/docs/src/08_widgets_in_depth.md @@ -66,20 +66,6 @@ textbox fire some action (say doing a search) 300ms after the last keypress: {{#include ../book_examples/src/custom_widgets_md.rs:annoying_textbox}} ``` -## todo - -v controller, painter -- how to do layout - - how constraints work - - child widget, set_layout_rect - - paint bounds -- container widgets -- widgetpod & architecture -- commands and widgetid -- focus / active / hot -- request paint & request layout -- changing widgets at runtime - [`Controller`]: https://docs.rs/druid/0.7.0/druid/widget/trait.Controller.html [`Widget`]: ./widget.md [`Painter`]: https://docs.rs/druid/0.7.0/druid/widget/struct.Painter.html diff --git a/docs/src/more_information.md b/docs/src/09_more_information.md similarity index 99% rename from docs/src/more_information.md rename to docs/src/09_more_information.md index 288c415485..2495edd149 100644 --- a/docs/src/more_information.md +++ b/docs/src/09_more_information.md @@ -1,4 +1,5 @@ # More information + If you want more information about Druid this document contains links more tutorials, blogposts and youtube videos. diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index cc4ccaca51..9bd19cbecd 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,14 +1,11 @@ # Summary -- [Overview](./intro.md) -- [Set up Druid](./setup.md) -- [Get started with Druid](./get_started.md) -- [Data trait](./data.md) -- [Widget trait](./widget.md) -- [Lens trait](./lens.md) -- [Env](./env.md) -- [Resolution independence](./resolution_independence.md) -- [Localization (TODO)](./localization.md) -- [Command (TODO)](./command.md) -- [Widgets in depth (TODO)](./custom_widgets.md) +- [Overview](./01_overview.md) +- [Getting started](./02_getting_started.md) +- [Data trait](./03_data.md) +- [Widget trait](./04_widget.md) +- [Lens trait](./05_lens.md) +- [Env](./06_env.md) +- [Resolution independence](./07_resolution_independence.md) +- [Widgets in depth](./08_widgets_in_depth.md) --- -- [More information](./more_information.md) +- [More information](./09_more_information.md) diff --git a/docs/src/command.md b/docs/src/command.md deleted file mode 100644 index 9113ce784a..0000000000 --- a/docs/src/command.md +++ /dev/null @@ -1 +0,0 @@ -# Command (TODO) diff --git a/docs/src/data.md b/docs/src/data.md deleted file mode 100644 index 06bd781b04..0000000000 --- a/docs/src/data.md +++ /dev/null @@ -1,47 +0,0 @@ -# Model data and the `Data` trait - -The heart of a Druid application is your application model. Your model drives -your UI. When you mutate your model, Druid compares the old and new version, -and propagates the change to the components ('widgets') of your application that -are affected by the change. - -For this to work, your model must implement the `Clone` and `Data` traits. It -is important that your model be cheap to clone; we encourage the use of -reference counted pointers to allow cheap cloning of more expensive types. `Arc` -and `Rc` have blanket `Data` impls, so if you have a type that does not -implement `Data`, you can always just wrap it in one of those smart pointers. - -The `Data` trait has a single method: - -```rust,no_run,noplaypen -{{#include ../../druid/src/data.rs:same_fn}} -``` - -#### Derive - -`Data` can be derived. This is recursive; it requires `Data` to be implemented -for all members. For 'C style' enums (enums where no variant has any fields) -this also requires an implementation of `PartialEq`. `Data` is implemented for -a number of `std` types, including all primitive types, `String`, `Arc`, `Rc`, -as well as `Option`, `Result`, and various tuples whose members implement -`Data`. - -Here is an example of using `Data` to implement a simple data model. - -```rust -{{#include ../book_examples/src/data_md.rs:derive}} -``` - -#### Collections - -`Data` is expected to be cheap to clone and cheap to compare, which can cause -issues with collection types. For this reason, `Data` is not implemented for -`std` types like `Vec` or `HashMap`. This is not a huge issue, however; you can -always put these types inside an `Rc` or an `Arc`, or if you're dealing with -larger collections you can build Druid with the `im` feature, which brings in -the [`im crate`], and adds a `Data` impl for the collections there. The [`im` -crate] is a collection of immutable data structures that act a lot like the `std` -collections, but can be cloned efficiently. - - -[`im` crate]: https://docs.rs/im diff --git a/docs/src/get_started.md b/docs/src/get_started.md deleted file mode 100644 index 75c1fdde20..0000000000 --- a/docs/src/get_started.md +++ /dev/null @@ -1,99 +0,0 @@ -# Get started with Druid -*this is outdated, and should be replaced with a walkthrough of getting a simple -app built and running*. - -This chapter will walk you through setting up a simple Druid application from start to finish. - -## Set up a Druid project -Setting up a project is a simple as creating a new Rust project; -```bash -> cargo new druid-example -``` - -And then adding Druid as a dependency to Cargo.toml -```toml -[dependencies] -druid = { git = "https://github.com/linebender/druid.git" } -``` - -To show a minimal window with a label replace `main.rs` with this; -```rust, noplaypen -use druid::{AppLauncher, WindowDesc, Widget, PlatformError}; -use druid::widget::Label; - -fn build_ui() -> impl Widget<()> { - Label::new("Hello world") -} - -fn main() -> Result<(), PlatformError> { - AppLauncher::with_window(WindowDesc::new(build_ui())).launch(())?; - Ok(()) -} -``` -In our main function we create an `AppLauncher`, pass it a `WindowDesc`, and launch it. We use `build_ui` to create a tree of widgets to pass to our `WindowDesc`. For now this tree consists of one simple label widget. - -This is a very simple example application and it's missing some important pieces. We will add these in the coming few paragraphs. - -## Draw more widgets -The first thing we could do to make our example application more interesting is to draw more than one widget. Unfortunately `WindowDesc::new` expects a function that returns only one Widget. We also need a way to tell Druid how to lay-out our widgets. -We solve both these problems by passing in a widget-tree with one single widget at the top. Widgets can have children and widgets higher up in the tree know how to lay-out their children. That way we describe a window as a widget-tree with layout containers as the branches and widgets as the leaves. Our `build_ui` function is then responsible for building this widget tree. - -To see how this works we will divide our window in four. We'll have two rows and two columns with a single label in each of the quadrants. We can lay-out our labels using the `Flex` widget. - -```rust, noplaypen -fn build_ui() -> impl Widget<()> { - Flex::row() - .with_flex_child( - Flex::column() - .with_flex_child(Label::new("top left"), 1.0) - .with_flex_child(Label::new("bottom left"), 1.0), - 1.0) - .with_flex_child( - Flex::column() - .with_flex_child(Label::new("top right"), 1.0) - .with_flex_child(Label::new("bottom right"), 1.0), - 1.0) -} -``` - -This looks nice but the labels on the left are drawn right against the window edge, so we needs some padding. Lets say we also want to center the two bottom labels. Unlike many other UI frameworks, widgets in Druid don't have padding or alignment properties themselves. Widgets are kept as simple as possible. - -Features like padding or alignment are implemented in separate widgets. To add padding you simply wrap the labels in a `Padding` widget. Centering widgets is done using the `Align` widget set to `centered`. - -```rust, noplaypen -fn build_ui() -> impl Widget<()> { - Padding::new( - 10.0, - Flex::row() - .with_flex_child( - Flex::column() - .with_flex_child(Label::new("top left"), 1.0) - .with_flex_child(Align::centered(Label::new("bottom left")), 1.0), - 1.0) - .with_flex_child( - Flex::column() - .with_flex_child(Label::new("top right"), 1.0) - .with_flex_child(Align::centered(Label::new("bottom right")), 1.0), - 1.0)) -} -``` - -Do not forget to import the new widgets; -```rust, noplaypen -use druid::widget::{Label, Flex, Padding, Align}; -``` - -## Application state -We can display a window and draw and position widgets in it. Now it's time to find out how we can tie these widgets to -the rest of our application. First lets see how we can display information from our application in the user interface. -For this we need to define what our application's state looks like. - -... - -## Handle user input - -... - -## Putting it all together - -... diff --git a/docs/src/localization.md b/docs/src/localization.md deleted file mode 100644 index 9b24bc685f..0000000000 --- a/docs/src/localization.md +++ /dev/null @@ -1 +0,0 @@ -# Localization (TODO) diff --git a/docs/src/setup.md b/docs/src/setup.md deleted file mode 100644 index 6ae32c3cee..0000000000 --- a/docs/src/setup.md +++ /dev/null @@ -1,47 +0,0 @@ -# Set up Druid -This tutorial assumes basic familliarity with Rust and a working setup with the basic tooling like -Rustup and Cargo. This tutorial will use stable Rust (v1.39.0 at the time of writing) and the latest -released version of Druid. - -This tutorial will first walk you through setting up the dependencies for developing a Druid -application, then it will show you how to set up a basic application, build it and run it. - -## Setting up Druid dependencies -In addition to including the `druid` library in your project - -### Linux -On Linux, Druid requires gtk+3. - -On Ubuntu this can be installed with -```no_compile -sudo apt-get install libgtk-3-dev -``` - -On Fedora -```no_compile -sudo dnf install gtk3-devel glib2-devel -``` - -See [GTK installation page] for more installation instructions. - -### OpenBSD -On OpenBSD, Druid requires gtk+3; install from packages: -```no_compile -pkg_add gtk+3 -``` - -## Starting a project -Starting a project is as easy as creating an empty application with -```no_compile -cargo new my-application -``` -and adding the druid dependency to your Cargo.toml -```no_compile -[dependencies] -druid = "0.7.0" -// or to be on the bleeding edge: -druid = { git = "https://github.com/linebender/druid.git" } -``` -This book is written against the latest code on git, so some examples may not work with `0.7.0`. - -[GTK installation page]: https://www.gtk.org/docs/installations/linux/ diff --git a/druid-shell/src/backend/windows/mod.rs b/druid-shell/src/backend/windows/mod.rs index 7d77da681d..63e2212482 100644 --- a/druid-shell/src/backend/windows/mod.rs +++ b/druid-shell/src/backend/windows/mod.rs @@ -40,7 +40,7 @@ pub mod window; // // https://docs.microsoft.com/en-us/windows/win32/direct2d/devices-and-device-contexts // A Device Context, ID2D1DeviceContext, is available as of windows 7 platform update. This -// is the the minimum compatibility target for druid. We are not making an effort to do +// is the minimum compatibility target for druid. We are not making an effort to do // RenderTarget only. // // Basically, go from HwndRenderTarget or DxgiSurfaceRenderTarget (2d or 3d) to a Device Context. diff --git a/druid-shell/src/backend/windows/window.rs b/druid-shell/src/backend/windows/window.rs index 2cfb6a59a1..d6c692d0f9 100644 --- a/druid-shell/src/backend/windows/window.rs +++ b/druid-shell/src/backend/windows/window.rs @@ -141,7 +141,7 @@ pub enum PresentStrategy { /// we'd try to call the `WinHandler` again). /// /// The solution is that for every `WindowHandle` method that *wants* to return control to the -/// system's event loop, instead of doing that we queue up a deferrred operation and return +/// system's event loop, instead of doing that we queue up a deferred operation and return /// immediately. The deferred operations will run whenever the currently running `WinHandler` /// method returns. /// diff --git a/druid/Cargo.toml b/druid/Cargo.toml index 428dd0690e..09896d0c6c 100644 --- a/druid/Cargo.toml +++ b/druid/Cargo.toml @@ -28,7 +28,6 @@ svg = ["usvg"] x11 = ["druid-shell/x11"] # **WARNING** not ready for the prime time. Many things don't work yet. wayland = ["druid-shell/wayland"] -crochet = [] serde = ["im/serde", "druid-shell/serde"] # Implement HasRawWindowHandle for WindowHandle diff --git a/druid/examples/cursor.rs b/druid/examples/cursor.rs index f79565b056..b8a88b9ebf 100644 --- a/druid/examples/cursor.rs +++ b/druid/examples/cursor.rs @@ -82,7 +82,7 @@ struct AppState { cursor: Cursor, custom: Option, // To see what #[data(ignore)] does look at the docs.rs page on `Data`: - // https://docs.rs/druid/0.6.0/druid/trait.Data.html + // https://docs.rs/druid/latest/druid/trait.Data.html #[data(ignore)] custom_desc: CursorDesc, } diff --git a/druid/examples/event_viewer.rs b/druid/examples/event_viewer.rs index c56a78cc0e..163bb8cd6c 100644 --- a/druid/examples/event_viewer.rs +++ b/druid/examples/event_viewer.rs @@ -95,7 +95,7 @@ struct LoggedEvent { typ: EventType, number: usize, // To see what #[data(ignore)] does look at the docs.rs page on `Data`: - // https://docs.rs/druid/0.6.0/druid/trait.Data.html + // https://docs.rs/druid/latest/druid/trait.Data.html #[data(ignore)] mouse: Option, #[data(ignore)] diff --git a/druid/examples/game_of_life.rs b/druid/examples/game_of_life.rs index d75de9c08b..f2711edff6 100644 --- a/druid/examples/game_of_life.rs +++ b/druid/examples/game_of_life.rs @@ -34,7 +34,7 @@ const POOL_SIZE: usize = GRID_SIZE * GRID_SIZE; const BACKGROUND: Color = Color::grey8(23); static COLOURS: ColorScheme = &[ Color::rgb8(0xEB, 0xF1, 0xF7), //Color::rgb(235, 241, 247) - Color::rgb8(0xA3, 0xFC, 0xF7), //Color::rgb(162,252,247) + Color::rgb8(0xA2, 0xFC, 0xF7), //Color::rgb(162,252,247) Color::rgb8(0xA2, 0xE3, 0xD8), //Color::rgb(162,227,216) Color::rgb8(0xF2, 0xE6, 0xF1), //Color::rgb(242,230,241) Color::rgb8(0xE0, 0xAF, 0xAF), //Color::rgb(224,175,175) diff --git a/druid/examples/identity.rs b/druid/examples/identity.rs index 0617294b62..235aaaeeb0 100644 --- a/druid/examples/identity.rs +++ b/druid/examples/identity.rs @@ -63,7 +63,7 @@ fn make_ui() -> impl Widget { // We can also generate these dynamically whenever we need it. let id_two = WidgetId::next(); // We have a column with 2 labels and 2 buttons. - // Each of the 2 labels only have access to their own counter and are given a `WidgetId`. + // Each of the 2 labels only has access to its own counter and is given a `WidgetId`. // Both labels have a controller, this handles commands send to children. // The 2 buttons send a command when clicked. Both send the exact same command. // The key diference is that they both give a different `WidgetId` as target. diff --git a/druid/examples/layout.rs b/druid/examples/layout.rs index 28d3690e6f..0597b5cf5f 100644 --- a/druid/examples/layout.rs +++ b/druid/examples/layout.rs @@ -40,7 +40,7 @@ fn build_app() -> impl Widget { ) // Spacing element that will fill all available space in // between label and a button. Notice that weight is non-zero. - // We could have achieved a similair result with expanding the + // We could have achieved a similar result with expanding the // width and setting the main-axis-allignment to SpaceBetween. .with_flex_spacer(1.0) .with_child(Button::new("Two").padding(20.)) diff --git a/druid/examples/readme.md b/druid/examples/readme.md index 4b8f35ffef..b986532de3 100644 --- a/druid/examples/readme.md +++ b/druid/examples/readme.md @@ -128,13 +128,11 @@ cargo run --example panels ``` Very similar to [layout](#Layout) but it splits the screen into 2 segments -## Value Formatting - -To run this example, make sure you are in `druid/examples/value_formatting` -And then run `cargo run` - -Druid doesn't have numeric specific texboxes, instead you have to parse the input as if it were a numeric value. -This example shows you how to parse, and validate text input. +## Scroll +``` +cargo run --example scroll +``` +Scrolling is a great way to show more content then can be displayed on the screen at a time. This is an example showing you how to use them. ## Split ``` @@ -144,17 +142,11 @@ cargo run --example split_demo The split widget allows you to put multiple widgets next, or on top of each other. This also allows the user to resize them. -## Scroll +## Slider +This shows the settings of Slider and RangeSlider, to let the user pick a value in a range. ``` -cargo run --example scroll +cargo run --example slider ``` -Scrolling is a great way to show more content then can be displayed on the screen at a time. This is an example showing you how to use them. - -## Split -``` -cargo run --example split_demo -``` -An example of how to split a widget in 2 in various ways. This also includes having the user drag the border!! ## Sub Window Not working, no sub-window seen? @@ -206,12 +198,21 @@ cargo run --example transparency ``` This shows you how to make the window transparent, so the rest of the desktop shows behind it. +## Value Formatting + +To run this example, make sure you are in `druid/examples/value_formatting` +And then run `cargo run` + +Druid doesn't have numeric specific texboxes, instead you have to parse the input as if it were a numeric value. +This example shows you how to parse, and validate text input. + ## View Switcher ``` cargo run --example view_switcher ``` Very similar to [tabs](#Tabs) but this allows you to have more control over it. This allows you to switch out widgets on the fly. + # Showcases ## Calc diff --git a/druid/examples/value_formatting/src/widgets.rs b/druid/examples/value_formatting/src/widgets.rs index 4a54e1710c..0b7b7c07a5 100644 --- a/druid/examples/value_formatting/src/widgets.rs +++ b/druid/examples/value_formatting/src/widgets.rs @@ -119,7 +119,7 @@ pub fn active_value() -> impl Widget { /// clears or sets this error based on `Command`s sent to it from some other /// widget. /// -/// It's child's data is this `Option`; the incoming data is ignored +/// Its child's data is this `Option`; the incoming data is ignored /// completely. pub struct ErrorController { child: WidgetPod, W>, diff --git a/druid/src/app.rs b/druid/src/app.rs index bd6e803583..a93d446c30 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -67,6 +67,11 @@ pub struct WindowConfig { } /// A description of a window to be instantiated. +/// +/// This struct has builder methods to specify some window properties. Each of +/// these methods usually corresponds to a platform API call when constructing the +/// function. Except for `title()`, they have no default values and the APIS +/// won't be called if the method is not used. pub struct WindowDesc { pub(crate) pending: PendingWindow, pub(crate) config: WindowConfig, @@ -469,6 +474,8 @@ impl WindowDesc { /// a `String`, a [`LocalizedString`], or a closure that computes a string; /// it will be kept up to date as the application's state changes. /// + /// If this method isn't called, the default title will be `LocalizedString::new("app-name")`. + /// /// [`LabelText`]: widget/enum.LocalizedString.html /// [`LocalizedString`]: struct.LocalizedString.html pub fn title(mut self, title: impl Into>) -> Self { diff --git a/druid/src/command.rs b/druid/src/command.rs index 7ddc759073..0360d4a5e5 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -247,7 +247,7 @@ pub mod sys { /// Show the application preferences. pub const SHOW_PREFERENCES: Selector = Selector::new("druid-builtin.menu-show-preferences"); - /// Show the application about window. + /// Show the application's "about" window. pub const SHOW_ABOUT: Selector = Selector::new("druid-builtin.menu-show-about"); /// Show all applications. @@ -271,7 +271,7 @@ pub mod sys { /// [`FileInfo`]: ../struct.FileInfo.html pub const OPEN_FILE: Selector = Selector::new("druid-builtin.open-file-path"); - /// Open a path, must be handled by the application. + /// Open a set of paths, must be handled by the application. /// /// [`FileInfo`]: ../struct.FileInfo.html pub const OPEN_FILES: Selector> = Selector::new("druid-builtin.open-files-path"); @@ -335,18 +335,18 @@ pub mod sys { pub(crate) const INVALIDATE_IME: Selector = Selector::new("druid-builtin.invalidate-ime"); - /// Informs this widget, that a child wants a specific region to be shown. The payload is the + /// Informs this widget that a child wants a specific region to be shown. The payload is the /// requested region in global coordinates. /// - /// This notification is send when [`scroll_to_view`] or [`scroll_area_to_view`] - /// are called. + /// This notification is sent when [`scroll_to_view`] or [`scroll_area_to_view`] + /// is called. /// - /// Widgets which hide their children, should always call `ctx.set_handled()` in response to + /// Widgets which hide their children should always call `ctx.set_handled()` when receiving this to /// avoid unintended behaviour from widgets further down the tree. /// If possible the widget should move its children to bring the area into view and then submit /// a new `SCROLL_TO_VIEW` notification with the same region relative to the new child position. /// - /// When building a new widget using ClipBox take a look at [`ClipBox::managed`] and + /// When building a new widget using ClipBox, take a look at [`ClipBox::managed`] and /// [`Viewport::default_scroll_to_view_handling`]. /// /// [`scroll_to_view`]: crate::EventCtx::scroll_to_view() diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 6081d7f833..1c73a01bd5 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -117,7 +117,7 @@ pub struct UpdateCtx<'a, 'b> { pub(crate) env: &'a Env, } -/// A context provided to layout handling methods of widgets. +/// A context provided to layout-handling methods of widgets. /// /// As of now, the main service provided is access to a factory for /// creating text layout objects, which are likely to be useful @@ -390,34 +390,18 @@ impl_context_method!( content_origin + self.to_window(widget_point).to_vec2() } - /// The "hot" (aka hover) status of a widget. + /// Query the "hot" state of the widget. /// - /// A widget is "hot" when the mouse is hovered over it. Widgets will - /// often change their appearance as a visual indication that they - /// will respond to mouse interaction. - /// - /// The hot status is computed from the widget's layout rect. In a - /// container hierarchy, all widgets with layout rects containing the - /// mouse position have hot status. - /// - /// Discussion: there is currently some confusion about whether a - /// widget can be considered hot when some other widget is active (for - /// example, when clicking to one widget and dragging to the next). - /// The documentation should clearly state the resolution. + /// See [`WidgetPod::is_hot`](struct.WidgetPod.html#method.is_hot) for + /// additional information. pub fn is_hot(&self) -> bool { self.widget_state.is_hot } - /// The active status of a widget. - /// - /// Active status generally corresponds to a mouse button down. Widgets - /// with behavior similar to a button will call [`set_active`] on mouse - /// down and then up. - /// - /// When a widget is active, it gets mouse events even when the mouse - /// is dragged away. + /// Query the "active" state of the widget. /// - /// [`set_active`]: struct.EventCtx.html#method.set_active + /// See [`WidgetPod::is_active`](struct.WidgetPod.html#method.is_active) for + /// additional information. pub fn is_active(&self) -> bool { self.widget_state.is_active } @@ -1016,7 +1000,7 @@ impl LifeCycleCtx<'_, '_> { self.widget_state.children.add(&child_id); } - /// Register this widget to be eligile to accept focus automatically. + /// Register this widget to be eligible to accept focus automatically. /// /// This should only be called in response to a [`LifeCycle::BuildFocusChain`] event. /// diff --git a/druid/src/core.rs b/druid/src/core.rs index b409898d83..d50f5b7f13 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -226,20 +226,48 @@ impl> WidgetPod { self.state.has_focus } - /// Query the "active" state of the widget. + /// The "active" (aka pressed) status of a widget. + /// + /// Active status generally corresponds to a mouse button down. Widgets + /// with behavior similar to a button will call [`set_active`] on mouse + /// down and then up. + /// + /// The active status can only be set manually. Druid doesn't automatically + /// set it to `false` on mouse release or anything like that. + /// + /// There is no special handling of the active status for multi-pointer devices. + /// + /// When a widget is active, it gets mouse events even when the mouse + /// is dragged away. + /// + /// [`set_active`]: struct.EventCtx.html#method.set_active pub fn is_active(&self) -> bool { self.state.is_active } - /// Returns `true` if any descendant is active. + /// Returns `true` if any descendant is [`active`]. + /// + /// [`active`]: struct.WidgetPod.html#method.is_active pub fn has_active(&self) -> bool { self.state.has_active } - /// Query the "hot" state of the widget. + /// The "hot" (aka hover) status of a widget. + /// + /// A widget is "hot" when the mouse is hovered over it. Some Widgets (eg buttons) + /// will change their appearance when hot as a visual indication that they + /// will respond to mouse interaction. + /// + /// The hot status is automatically computed from the widget's layout rect. In a + /// container hierarchy, all widgets with layout rects containing the mouse position + /// have hot status. The hot status cannot be set manually. + /// + /// There is no special handling of the hot status for multi-pointer devices. /// - /// See [`EventCtx::is_hot`](struct.EventCtx.html#method.is_hot) for - /// additional information. + /// Note: a widget can be hot while another is [`active`] (for example, when + /// clicking a button and dragging the cursor to another widget). + /// + /// [`active`]: struct.WidgetPod.html#method.is_active pub fn is_hot(&self) -> bool { self.state.is_hot } @@ -594,23 +622,6 @@ impl> WidgetPod { } } - /// Execute the closure with this widgets `EventCtx`. - #[cfg(feature = "crochet")] - pub fn with_event_context(&mut self, parent_ctx: &mut EventCtx, mut fun: F) - where - F: FnMut(&mut W, &mut EventCtx), - { - let mut ctx = EventCtx { - state: parent_ctx.state, - widget_state: &mut self.state, - notifications: parent_ctx.notifications, - is_handled: false, - is_root: false, - }; - fun(&mut self.inner, &mut ctx); - parent_ctx.widget_state.merge_up(&mut self.state); - } - /// Propagate an event. /// /// Generally the [`event`] method of a container widget will call this diff --git a/druid/src/data.rs b/druid/src/data.rs index c504f61134..bbda531f3b 100644 --- a/druid/src/data.rs +++ b/druid/src/data.rs @@ -207,6 +207,7 @@ impl Data for f64 { } } +/// Checks pointer equality. The internal value is not checked. impl Data for Arc { fn same(&self, other: &Self) -> bool { Arc::ptr_eq(self, other) diff --git a/druid/src/event.rs b/druid/src/event.rs index b9c786e99a..cfb1b556e5 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -172,7 +172,7 @@ pub enum Event { /// A [`Notification`] from one of this widget's descendants. /// /// While handling events, widgets can submit notifications to be - /// delivered to their ancestors immdiately after they return. + /// delivered to their ancestors immediately after they return. /// /// If you handle a [`Notification`], you should call [`EventCtx::set_handled`] /// to stop the notification from being delivered to further ancestors. @@ -405,7 +405,7 @@ impl Event { /// This distinction between scroll and tabs is due to one of the main purposes of /// this method: determining which widgets are allowed to receive focus. As a rule /// of thumb a widget counts as `hidden` if it makes no sense for it to receive focus - /// when the user presses thee 'tab' key. + /// when the user presses the 'tab' key. /// /// If a widget changes which children are hidden it must call [`children_changed`]. /// diff --git a/druid/src/lib.rs b/druid/src/lib.rs index 4a25193dc1..ca023102a8 100644 --- a/druid/src/lib.rs +++ b/druid/src/lib.rs @@ -33,7 +33,7 @@ //! their state and redraw. //! //! As your application grows, you can use [`Lens`]es to expose only certain -//! subsets of your data model to certains subsets of your widget tree. +//! subsets of your data model to certain subsets of your widget tree. //! //! For more information you should read the [druid book]. //! @@ -93,11 +93,34 @@ //! //! # Optional Features //! +//! Utility features: +//! //! * `im` - Efficient immutable data structures using the [`im` crate], //! which is made available via the [`im` module]. //! * `svg` - Scalable Vector Graphics for icons and other scalable images using the [`usvg` crate]. //! * `image` - Bitmap image support using the [`image` crate]. //! * `x11` - Work-in-progress X11 for Linux and OpenBSD backend instead of GTK. +//! * `wayland` - Work-in progress Wayland backend, very experimental. +//! * `serde` - Serde support for some internal types (most Kurbo primitives). +//! +//! Image format features: +//! +//! - png +//! - jpeg +//! - jpeg_rayon +//! - gif +//! - bmp +//! - ico +//! - tiff +//! - webp +//! - pnm +//! - dds +//! - tga +//! - farbfeld +//! - dxt +//! - hdr +//! +//! You can enable all these formats with `image-all`. //! //! Features can be added with `cargo`. For example, in your `Cargo.toml`: //! ```no_compile diff --git a/druid/src/tests/harness.rs b/druid/src/tests/harness.rs index 4fc584520c..65e0beaa77 100644 --- a/druid/src/tests/harness.rs +++ b/druid/src/tests/harness.rs @@ -117,7 +117,7 @@ impl Harness<'_, T> { /// /// The create functions are used to test a widget. The function takes a `root` widget /// and a data structure and uses them to create a `Harness`. The Harness can then be interacted - /// with via the `harness_closure` callback. The the final render of + /// with via the `harness_closure` callback. The final render of /// the widget can be inspected with the `render_context_closure` callback. /// /// # Arguments diff --git a/druid/src/text/format.rs b/druid/src/text/format.rs index 86ff606836..df026cf7e6 100644 --- a/druid/src/text/format.rs +++ b/druid/src/text/format.rs @@ -106,7 +106,7 @@ pub struct ValidationError { /// A naive [`Formatter`] for types that implement [`FromStr`]. /// /// For types that implement [`std::fmt::Display`], the [`ParseFormatter::new`] -/// constructor creates a formatter that format's it's value using that trait. +/// constructor creates a formatter that format's its value using that trait. /// If you would like to customize the formatting, you can use the /// [`ParseFormatter::with_format_fn`] constructor, and pass your own formatting /// function. diff --git a/druid/src/widget/clip_box.rs b/druid/src/widget/clip_box.rs index 4d9d77816f..42261905f6 100644 --- a/druid/src/widget/clip_box.rs +++ b/druid/src/widget/clip_box.rs @@ -144,7 +144,7 @@ impl Viewport { /// The default handling of the [`SCROLL_TO_VIEW`] notification for a scrolling container. /// - /// The [`SCROLL_TO_VIEW`] notification is send when [`scroll_to_view`] or [`scroll_area_to_view`] + /// The [`SCROLL_TO_VIEW`] notification is sent when [`scroll_to_view`] or [`scroll_area_to_view`] /// are called. /// /// [`SCROLL_TO_VIEW`]: crate::commands::SCROLL_TO_VIEW @@ -182,7 +182,7 @@ impl Viewport { /// This method handles SCROLL_TO_VIEW by clipping the view_rect to the content rect. /// - /// The [`SCROLL_TO_VIEW`] notification is send when [`scroll_to_view`] or [`scroll_area_to_view`] + /// The [`SCROLL_TO_VIEW`] notification is sent when [`scroll_to_view`] or [`scroll_area_to_view`] /// are called. /// /// [`SCROLL_TO_VIEW`]: crate::commands::SCROLL_TO_VIEW diff --git a/druid/src/widget/common.rs b/druid/src/widget/common.rs index a21cb8bcbd..a60b2a1938 100644 --- a/druid/src/widget/common.rs +++ b/druid/src/widget/common.rs @@ -14,7 +14,7 @@ use crate::{Affine, Data, Size}; -// These are based on https://api.flutter.dev/flutter/painting/BoxFit-class.html +// These are based on https://api.flutter.dev/flutter/painting/BoxFit.html /// Strategies for inscribing a rectangle inside another rectangle. #[derive(Clone, Data, Copy, PartialEq, Eq)] pub enum FillStrat { diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index 52325c79c8..9fd9312828 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -295,14 +295,14 @@ impl Axis { pub enum CrossAxisAlignment { /// Top or leading. /// - /// In a vertical container, widgets are top aligned. In a horiziontal + /// In a vertical container, widgets are top aligned. In a horizontal /// container, their leading edges are aligned. Start, /// Widgets are centered in the container. Center, /// Bottom or trailing. /// - /// In a vertical container, widgets are bottom aligned. In a horiziontal + /// In a vertical container, widgets are bottom aligned. In a horizontal /// container, their trailing edges are aligned. End, /// Align on the baseline. @@ -571,7 +571,7 @@ impl Flex { flex: params.flex, } } else { - tracing::warn!("Flex value should be > 0.0. To add a non-flex child use the add_child or with_child methods.\nSee the docs for more information: https://docs.rs/druid/0.7.0/druid/widget/struct.Flex.html"); + tracing::warn!("Flex value should be > 0.0. To add a non-flex child use the add_child or with_child methods.\nSee the docs for more information: https://docs.rs/druid/0.8.0/druid/widget/struct.Flex.html"); Child::Fixed { widget: WidgetPod::new(Box::new(child)), alignment: None, diff --git a/druid/src/widget/slider.rs b/druid/src/widget/slider.rs index 36949ab991..4a6d28dd00 100644 --- a/druid/src/widget/slider.rs +++ b/druid/src/widget/slider.rs @@ -143,7 +143,7 @@ impl Slider { self } - /// Builder-style method to the the axis on which the slider moves. + /// Builder-style method to the axis on which the slider moves. /// /// The default is `Horizontal`. pub fn axis(mut self, axis: Axis) -> Self { @@ -278,7 +278,7 @@ impl RangeSlider { self } - /// Builder-style method to the the axis on which the slider moves. + /// Builder-style method to set the axis on which the slider moves. /// /// The default is `Horizontal`. pub fn axis(mut self, axis: Axis) -> Self { diff --git a/druid/src/widget/split.rs b/druid/src/widget/split.rs index 47e2993030..96e53e208d 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -75,15 +75,19 @@ impl Split { } /// Create a new split panel, with the horizontal axis split in two by a vertical bar. - /// The children are laid out left and right. - pub fn columns(child1: impl Widget + 'static, child2: impl Widget + 'static) -> Self { - Self::new(Axis::Horizontal, child1, child2) + pub fn columns( + left_child: impl Widget + 'static, + right_child: impl Widget + 'static, + ) -> Self { + Self::new(Axis::Horizontal, left_child, right_child) } /// Create a new split panel, with the vertical axis split in two by a horizontal bar. - /// The children are laid out up and down. - pub fn rows(child1: impl Widget + 'static, child2: impl Widget + 'static) -> Self { - Self::new(Axis::Vertical, child1, child2) + pub fn rows( + upper_child: impl Widget + 'static, + lower_child: impl Widget + 'static, + ) -> Self { + Self::new(Axis::Vertical, upper_child, lower_child) } /// Builder-style method to set the split point as a fraction of the split axis. diff --git a/druid/src/widget/stepper.rs b/druid/src/widget/stepper.rs index 55ae97f0b2..3ca3bf39ba 100644 --- a/druid/src/widget/stepper.rs +++ b/druid/src/widget/stepper.rs @@ -24,9 +24,9 @@ use crate::piet::{LinearGradient, RenderContext, UnitPoint}; use crate::widget::prelude::*; use crate::{theme, Point, Rect, TimerToken}; -// Delay until stepper starts automatically changing valued when one of the button is held down. +// Delay until stepper starts automatically changing value when one of the buttons is held down. const STEPPER_REPEAT_DELAY: Duration = Duration::from_millis(500); -// Delay between value changes when one of the button is held down. +// Delay between value changes when one of the buttons is held down. const STEPPER_REPEAT: Duration = Duration::from_millis(200); /// A stepper widget for step-wise increasing and decreasing a value. diff --git a/druid/src/widget/svg.rs b/druid/src/widget/svg.rs index cce0758fe0..4d31a0cda7 100644 --- a/druid/src/widget/svg.rs +++ b/druid/src/widget/svg.rs @@ -53,7 +53,7 @@ impl Svg { self.fill = newfil; } - /// Set the svg data. + /// Set the SVG data. pub fn set_svg_data(&mut self, svg_data: SvgData) { self.svg_data = svg_data; } @@ -144,7 +144,7 @@ impl SvgData { } } - /// Get the viewbox for the svg. This is the area that should be drawn. + /// Get the viewbox for the SVG. This is the area that should be drawn. pub fn viewbox(&self) -> Rect { let root = self.tree.root(); let rect = match *root.borrow() { @@ -162,7 +162,7 @@ impl SvgData { rect } - /// Get the size of the svg. This is the size that the svg requests to be drawn. If it is + /// Get the size of the SVG. This is the size that the SVG requests to be drawn. If it is /// different from the viewbox size, then scaling will be required. pub fn size(&self) -> Size { let root = self.tree.root(); @@ -181,7 +181,7 @@ impl SvgData { rect } - /// Calculates the transform that should be applied first to the svg path data, to convert from + /// Calculates the transform that should be applied first to the SVG path data, to convert from /// image coordinates to piet coordinates. fn inner_affine(&self) -> Affine { let viewbox = self.viewbox(); diff --git a/druid/src/widget/z_stack.rs b/druid/src/widget/z_stack.rs index 4945a99fbd..a689f49762 100644 --- a/druid/src/widget/z_stack.rs +++ b/druid/src/widget/z_stack.rs @@ -88,7 +88,7 @@ impl ZStack { /// Builder-style method to add a new child to the Z-Stack. /// /// The child is added directly above the base layer, is positioned in the center and has no - /// size constrains. + /// size constraints. pub fn with_centered_child(self, child: impl Widget + 'static) -> Self { self.with_aligned_child(child, UnitPoint::CENTER) } @@ -96,7 +96,7 @@ impl ZStack { /// Builder-style method to add a new child to the Z-Stack. /// /// The child is added directly above the base layer, uses the given alignment and has no - /// size constrains. + /// size constraints. pub fn with_aligned_child(self, child: impl Widget + 'static, alignment: UnitPoint) -> Self { self.with_child( child, diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index d3e4a8864b..d8647b16e5 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -641,7 +641,7 @@ impl AppState { } } - /// Handle a 'command' message from druid-shell. These map to an item + /// Handle a 'command' message from druid-shell. These map to an item /// in an application, window, or context (right-click) menu. /// /// If the menu is associated with a window (the general case) then