Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Layout and Styling chapter #16

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
- [Architecture](architecture.md)
- [First Steps](first-steps.md)
- [The Runtime](the-runtime.md)
- [Layout](layout.md)
- [Styling](styling.md)

- [More to come!]()

<!--
- [Layout]()
- [Styling]()
- [Concurrency]()

# Scaling Applications
Expand Down
120 changes: 120 additions & 0 deletions src/layout.md
Original file line number Diff line number Diff line change
@@ -1 +1,121 @@
# Layout
After learning about the fundamentals of The Elm Architecture and how to run our applications, it's time to explore how we can arrange our widgets visually on the screen.

In iced, layout is handled in a modular way - each widget implements its own layout strategy rather than having a single unified system. This gives us flexibility while keeping things simple and predictable.

## Building Blocks
The most common way to build layouts in iced is by combining three fundamental layout widgets:

- [`row`] - Arrange widgets horizontally from left to right
- [`column`] - Stack widgets vertically from top to bottom
- [`container`] - Position or align a single widget within their bounds

Let's see how we can use these building blocks together:

```rust,ignore,iced(height=140px)
# use iced::{
# widget::{
# column,
# container,
# row,
# },
# Length,
# Element,
# };
#
# #[derive(Default)]
# struct Example;
#
# #[derive(Debug, Clone, Copy)]
# enum Message { }
# impl Example {
# fn update(&mut self, _message: Message) {}
#
fn view(&self) -> Element<Message> {
// Create container
container(
// Create column
column![
"Top",
// Create row with spacing "10"
row![
"Left",
// Create column without spacing
column![
"Center Top",
"Center Bottom"
],
"Right"
].spacing(10),
"Bottom"
]
// Add spacing "10"
.spacing(10),
)
// Add padding "10"
.padding(10)
// Center horizontally
.center_x(Length::Fill)
// Center vertically
.center_y(Length::Fill)
.into()
}
# }
#
# pub fn main() -> iced::Result {
# iced::run("Layout example", Example::update, Example::view)
# }
```

Let's break down what's happening in this example:

1. We create a [`column`] that stacks three elements vertically:
- A "Top" text widget
- A [`row`] containing "Left" and "Right" text widgets and [`row`] with "Center Top" and "Center Bottom" text widgets
- A "Bottom" text widget

2. We add spacing between the column's and row's elements using `.spacing(10)`

3. The row also gets horizontal spacing between its elements

4. We wrap everything in a [`container`] that:
- Adds padding around its contents
- Centers the content both horizontally and vertically

## Spacing and Padding
Both rows and columns support adding space between their children using the [`.spacing()`] method. This is different from padding:

- __Spacing__ adds space between elements
- __Padding__ adds space around all elements

Containers can add padding around their contents using [`.padding()`]. This creates breathing room between the container's bounds and its child widget.

## Alignment
Containers are particularly useful for alignment. They can position their contents using methods like:

- [`.center_x()`] - Center horizontally
- [`.center_y()`] - Center vertically
- [`.align_x()`] - Align horizontally (left, center, right)
- [`.align_y()`] - Align vertically (top, center, bottom)

First two methods accept a parameter of type [`Length`]. This allows us to specify how much of the available space we want to use.

[`.align_x()`] accepts [`Horizontal`] enum, [`.align_y`] accepts [`Vertical`] enum.

> The layout system in iced is intentionally simple and predictable. Rather than having a complex unified system,
> it provides these basic building blocks that can be composed to create more sophisticated layouts.

This modular approach means each widget can implement the layout behavior that makes the most sense for its purpose, while still being able to work together seamlessly through composition.

[`Length]: https://docs.iced.rs/iced/enum.Length.html
[`Horizontal`]: https://docs.iced.rs/iced/alignment/enum.Horizontal.html
[`Vertical`]: https://docs.iced.rs/iced/alignment/enum.Vertical.html
[`.center_x()`]: https://docs.iced.rs/iced/widget/container/struct.Container.html#method.center_x
[`.center_y()`]: https://docs.iced.rs/iced/widget/container/struct.Container.html#method.center_y
[`.align_x()`]: https://docs.iced.rs/iced/widget/container/struct.Container.html#method.align_x
[`.align_y()`]: https://docs.iced.rs/iced/widget/container/struct.Container.html#method.align_y
[`row`]: https://docs.iced.rs/iced/widget/macro.row.html
[`column`]: https://docs.iced.rs/iced/widget/macro.column.html
[`container`]: https://docs.iced.rs/iced/widget/container/index.html
[`.spacing()`]: https://docs.iced.rs/iced/widget/struct.Column.html#method.spacing
[`.padding()`]: https://docs.iced.rs/iced/widget/container/struct.Container.html#method.padding
254 changes: 254 additions & 0 deletions src/styling.md
Original file line number Diff line number Diff line change
@@ -1 +1,255 @@
# Styling
After learning about layout and how to arrange widgets on the screen, let's explore how we can make our interfaces look more appealing and match our desired visual design.

Like with layout, iced takes a modular approach to styling - each widget implements its own styling strategy rather than having a unified system. However, all built-in widgets follow consistent styling patterns that we'll explore in this chapter.

## Basic Styling
The most straightforward way to style a widget in iced is by using its `.style()` method. Let's look at a simple example:

```rust,ignore,iced(height=100px)
# use iced::{
# widget::container,
# Element,
# };
#
# #[derive(Default)]
# struct Example;
# #[derive(Debug, Clone, Copy)]
# enum Message {}
#
# impl Example {
# fn update(&mut self, _message: Message) {
# // No state changes
# }
#
fn view(&self) -> Element<Message> {
container("I am a rounded box!")
.style(container::rounded_box)
.into()
}
# }
#
# pub fn main() -> iced::Result {
# iced::run("Styling example", Example::update, Example::view)
# }
```

Here we're using the built-in `rounded_box` style provided by the `container` module. Most widgets provide these pre-made styles for common use cases.

## Theme-Aware Styling
While using pre-made styles is convenient, you often want to create custom styles that adapt to the current theme of your application. The `.style()` method accepts a closure that receives the current [`Theme`] and returns the appropriate style:

```rust,ignore,iced(height=100px)
# use iced::{
# widget::button,
# Element,
# Theme,
# };
#
# #[derive(Default)]
# struct Example;
#
# #[derive(Debug, Clone, Copy)]
# enum Message {
# ButtonPressed,
# }
#
# impl Example {
# fn update(&mut self, _message: Message) {
# // No state changes
# }
#
fn view(&self) -> Element<Message> {
button("I am a styled button!")
# .on_press(Message::ButtonPressed)
.style(|theme: &Theme, _status| {
let palette = theme.extended_palette();

button::Style::default()
.with_background(palette.primary.base.color)
})
.into()
}
# }
#
# pub fn main() -> iced::Result {
# iced::run("Theme styling example", Example::update, Example::view)
# }
```

The [`Theme`] provides access to color palettes through two methods:
- `.palette()` - Basic color palette with primary colors
- `.extended_palette()` - Extended palette with additional color variations

## State-Based Styling
Many widgets can exist in different states - a button can be pressed, hovered, or disabled for example. When styling these widgets, the closure also receives a `Status` parameter allowing you to define different styles for each state:

```rust,ignore,iced(height=100px)
# use iced::{
# widget::button,
# Element,
# Theme,
# };
#
# #[derive(Default)]
# struct Example;
#
# #[derive(Debug, Clone, Copy)]
# enum Message {
# ButtonPressed,
# }
#
# impl Example {
# fn update(&mut self, _message: Message) {
# // No state changes
# }
#
fn view(&self) -> Element<Message> {
button("I am a state-aware button!")
# .on_press(Message::ButtonPressed)
.style(|theme: &Theme, status| {
let palette = theme.extended_palette();

match status {
button::Status::Active => {
button::Style::default()
.with_background(palette.success.strong.color)
}
button::Status::Hovered => {
button::Style::default()
.with_background(palette.success.base.color)
}
_ => button::primary(theme, status),
}
})
.into()
}
# }
#
# pub fn main() -> iced::Result {
# iced::run("State-based styling example", Example::update, Example::view)
# }
```
Also, you can return different styles based on app's state:
```rust,ignore,iced(height=100px)
# use iced::{
# widget::button,
# Element,
# Theme,
# };
#
#[derive(Default)]
struct Example {
is_feature_enabled: bool,
}

#[derive(Debug, Clone, Copy)]
enum Message {
ButtonPressed,
}

impl Example {
fn update(&mut self, message: Message) {
# #[allow(irrefutable_let_patterns)]
if let Message::ButtonPressed = message {
self.is_feature_enabled = !self.is_feature_enabled;
}
}

fn view(&self) -> Element<Message> {
button("I am an app's state-aware button! Click me to toggle the color.")
.on_press(Message::ButtonPressed)
.style(|theme: &Theme, _status| {
let palette = theme.extended_palette();

if self.is_feature_enabled {
button::Style::default()
.with_background(palette.success.strong.color)
} else {
button::Style::default()
.with_background(palette.success.base.color)
}
})
.into()
}
}
#
# pub fn main() -> iced::Result {
# iced::run("App's state-based styling example", Example::update, Example::view)
# }
```


## Built-in Styles
Most widget modules provide convenient styling functions that you can use directly, for example:

- [`container::rounded_box`] - Creates a container with rounded corners
- [`button::primary`] - Primary button style using theme colors
- [`text::danger`] - Red text style for error messages

Here's how you might use these built-in styles:

```rust,ignore,iced(height=100px)
# use iced::{
# widget::{button, container},
# Element,
# };
#
# #[derive(Default)]
# struct Example;
#
# #[derive(Debug, Clone, Copy)]
# enum Message {
# ButtonPressed,
# }
#
# impl Example {
# fn update(&mut self, _message: Message) {
# // No state changes
# }
#
fn view(&self) -> Element<Message> {
container(
button("Click me!")
# .on_press(Message::ButtonPressed)
.style(button::primary)
)
.style(container::rounded_box)
.into()
}
# }
#
# pub fn main() -> iced::Result {
# iced::run("Built-in styles example", Example::update, Example::view)
# }
```

> The styling system in iced is intentionally flexible. Rather than enforcing a single approach,
> it provides the building blocks needed to create consistent styles across your application
> while still allowing for customization when needed.

[`Theme`]: https://docs.rs/iced/latest/iced/enum.Theme.html
[`button::primary`]: https://docs.rs/iced/latest/iced/widget/button/fn.primary.html
[`container::rounded_box`]: https://docs.rs/iced/latest/iced/widget/container/fn.rounded_box.html
[`text::danger`]: https://docs.rs/iced/latest/iced/widget/text/fn.danger.html

> #### Note From the Author
> You reached the end of the book, for now!
>
> I think it should already serve as a quick introduction to the basics of the library.
> There is a lot more to unravel—but hopefully you are now at a point where you can start
> playing around, having fun, and experimenting further.
>
> The book is far from finished—there are a lot more topics I want to cover here, namely:
>
> - Concurrency
> - Scaling Applications
> - Extending the Runtime
> - And More!
>
> Until I get to write them, check out the [Additional Resources](additional-resources.md)
> chapter if you want to explore and learn further.
>
> I hope that you enjoyed the read so far. Stay tuned!
>
> — Héctor
Loading