summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2019-11-14 06:48:32 +0100
committerLibravatar GitHub <noreply@github.com>2019-11-14 06:48:32 +0100
commitc8d4774704f970bb26eb5d9903b7a741b1c225e3 (patch)
tree1ceabb28d98c4f4cc9a556d2a5328ce8b7163130
parentaf5ec4941270c832557994d9b4cc70ce5feac911 (diff)
parent2c8ba652a7929ac6c2af28ac60a8bd4b8e8e2f10 (diff)
downloadiced-c8d4774704f970bb26eb5d9903b7a741b1c225e3.tar.gz
iced-c8d4774704f970bb26eb5d9903b7a741b1c225e3.tar.bz2
iced-c8d4774704f970bb26eb5d9903b7a741b1c225e3.zip
Merge pull request #56 from hecrj/example/filter-todos
Draw checkmark icon and filter todos
-rw-r--r--examples/todos.rs160
-rw-r--r--wgpu/src/renderer.rs18
-rw-r--r--wgpu/src/renderer/widget/checkbox.rs17
-rw-r--r--wgpu/src/text.rs13
-rw-r--r--wgpu/src/text/icons.ttfbin0 -> 4912 bytes
5 files changed, 172 insertions, 36 deletions
diff --git a/examples/todos.rs b/examples/todos.rs
index 028b2d65..f921a666 100644
--- a/examples/todos.rs
+++ b/examples/todos.rs
@@ -13,13 +13,16 @@ struct Todos {
scroll: scrollable::State,
input: text_input::State,
input_value: String,
+ filter: Filter,
tasks: Vec<Task>,
+ controls: Controls,
}
#[derive(Debug, Clone)]
pub enum Message {
InputChanged(String),
CreateTask,
+ FilterChanged(Filter),
TaskMessage(usize, TaskMessage),
}
@@ -41,6 +44,9 @@ impl Application for Todos {
self.input_value.clear();
}
}
+ Message::FilterChanged(filter) => {
+ self.filter = filter;
+ }
Message::TaskMessage(i, TaskMessage::Delete) => {
self.tasks.remove(i);
}
@@ -55,26 +61,39 @@ impl Application for Todos {
}
fn view(&mut self) -> Element<Message> {
+ let Todos {
+ scroll,
+ input,
+ input_value,
+ filter,
+ tasks,
+ controls,
+ } = self;
+
let title = Text::new("todos")
.size(100)
- .color(GRAY)
+ .color([0.5, 0.5, 0.5])
.horizontal_alignment(HorizontalAlignment::Center);
let input = TextInput::new(
- &mut self.input,
+ input,
"What needs to be done?",
- &self.input_value,
+ input_value,
Message::InputChanged,
)
.padding(15)
.size(30)
.on_submit(Message::CreateTask);
+ let controls = controls.view(&tasks, *filter);
+ let filtered_tasks = tasks.iter().filter(|task| filter.matches(task));
+
let tasks: Element<_> =
- if self.tasks.len() > 0 {
- self.tasks
+ if filtered_tasks.count() > 0 {
+ tasks
.iter_mut()
.enumerate()
+ .filter(|(_, task)| filter.matches(task))
.fold(Column::new().spacing(20), |column, (i, task)| {
column.push(task.view().map(move |message| {
Message::TaskMessage(i, message)
@@ -82,16 +101,11 @@ impl Application for Todos {
})
.into()
} else {
- Container::new(
- Text::new("You do not have any tasks! :D")
- .size(25)
- .horizontal_alignment(HorizontalAlignment::Center)
- .color([0.7, 0.7, 0.7]),
- )
- .width(Length::Fill)
- .height(Length::Units(200))
- .center_y()
- .into()
+ empty_message(match filter {
+ Filter::All => "You have not created a task yet...",
+ Filter::Active => "All your tasks are done! :D",
+ Filter::Completed => "You have not completed a task yet...",
+ })
};
let content = Column::new()
@@ -99,9 +113,10 @@ impl Application for Todos {
.spacing(20)
.push(title)
.push(input)
+ .push(controls)
.push(tasks);
- Scrollable::new(&mut self.scroll)
+ Scrollable::new(scroll)
.padding(40)
.push(Container::new(content).width(Length::Fill).center_x())
.into()
@@ -234,18 +249,115 @@ impl Task {
}
}
-// Colors
-const GRAY: Color = Color {
- r: 0.5,
- g: 0.5,
- b: 0.5,
- a: 1.0,
-};
+#[derive(Debug, Default)]
+pub struct Controls {
+ all_button: button::State,
+ active_button: button::State,
+ completed_button: button::State,
+}
+
+impl Controls {
+ fn view(&mut self, tasks: &[Task], current_filter: Filter) -> Row<Message> {
+ let Controls {
+ all_button,
+ active_button,
+ completed_button,
+ } = self;
+
+ let tasks_left = tasks.iter().filter(|task| !task.completed).count();
+
+ let filter_button = |state, label, filter, current_filter| {
+ let label = Text::new(label).size(16).width(Length::Shrink);
+ let button = if filter == current_filter {
+ Button::new(state, label.color(Color::WHITE))
+ .background(Background::Color([0.2, 0.2, 0.7].into()))
+ } else {
+ Button::new(state, label)
+ };
+
+ button
+ .on_press(Message::FilterChanged(filter))
+ .padding(8)
+ .border_radius(10)
+ };
+
+ Row::new()
+ .spacing(20)
+ .align_items(Align::Center)
+ .push(
+ Text::new(&format!(
+ "{} {} left",
+ tasks_left,
+ if tasks_left == 1 { "task" } else { "tasks" }
+ ))
+ .size(16),
+ )
+ .push(
+ Row::new()
+ .width(Length::Shrink)
+ .spacing(10)
+ .push(filter_button(
+ all_button,
+ "All",
+ Filter::All,
+ current_filter,
+ ))
+ .push(filter_button(
+ active_button,
+ "Active",
+ Filter::Active,
+ current_filter,
+ ))
+ .push(filter_button(
+ completed_button,
+ "Completed",
+ Filter::Completed,
+ current_filter,
+ )),
+ )
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Filter {
+ All,
+ Active,
+ Completed,
+}
+
+impl Default for Filter {
+ fn default() -> Self {
+ Filter::All
+ }
+}
+
+impl Filter {
+ fn matches(&self, task: &Task) -> bool {
+ match self {
+ Filter::All => true,
+ Filter::Active => !task.completed,
+ Filter::Completed => task.completed,
+ }
+ }
+}
+
+fn empty_message(message: &str) -> Element<'static, Message> {
+ Container::new(
+ Text::new(message)
+ .size(25)
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .color([0.7, 0.7, 0.7]),
+ )
+ .width(Length::Fill)
+ .height(Length::Units(200))
+ .center_y()
+ .into()
+}
// Fonts
const ICONS: Font = Font::External {
name: "Icons",
- bytes: include_bytes!("./resources/icons.ttf"),
+ bytes: include_bytes!("resources/icons.ttf"),
};
fn icon(unicode: char) -> Text {
diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs
index d3bdc878..52764248 100644
--- a/wgpu/src/renderer.rs
+++ b/wgpu/src/renderer.rs
@@ -344,11 +344,27 @@ impl Renderer {
for text in layer.text.iter() {
// Target physical coordinates directly to avoid blurry text
let text = wgpu_glyph::Section {
+ // TODO: We `round` here to avoid rerasterizing text when
+ // its position changes slightly. This can make text feel a
+ // bit "jumpy". We may be able to do better once we improve
+ // our text rendering/caching pipeline.
screen_position: (
(text.screen_position.0 * dpi).round(),
(text.screen_position.1 * dpi).round(),
),
- bounds: (text.bounds.0 * dpi, text.bounds.1 * dpi),
+ // TODO: Fix precision issues with some DPI factors.
+ //
+ // The `ceil` here can cause some words to render on the
+ // same line when they should not.
+ //
+ // Ideally, `wgpu_glyph` should be able to compute layout
+ // using logical positions, and then apply the proper
+ // DPI scaling. This would ensure that both measuring and
+ // rendering follow the same layout rules.
+ bounds: (
+ (text.bounds.0 * dpi).ceil(),
+ (text.bounds.1 * dpi).ceil(),
+ ),
scale: wgpu_glyph::Scale {
x: text.scale.x * dpi,
y: text.scale.y * dpi,
diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs
index c2d7911c..aedb821c 100644
--- a/wgpu/src/renderer/widget/checkbox.rs
+++ b/wgpu/src/renderer/widget/checkbox.rs
@@ -74,14 +74,15 @@ impl checkbox::Renderer for Renderer {
(
Primitive::Group {
primitives: if checkbox.is_checked {
- // TODO: Draw an actual icon
- let (check, _) = text::Renderer::draw(
- self,
- &Text::new("X")
- .horizontal_alignment(HorizontalAlignment::Center)
- .vertical_alignment(VerticalAlignment::Center),
- checkbox_layout,
- );
+ let check = Primitive::Text {
+ content: crate::text::CHECKMARK_ICON.to_string(),
+ font: crate::text::BUILTIN_ICONS,
+ size: checkbox_bounds.height * 0.7,
+ bounds: checkbox_bounds,
+ color: [0.3, 0.3, 0.3].into(),
+ horizontal_alignment: HorizontalAlignment::Center,
+ vertical_alignment: VerticalAlignment::Center,
+ };
vec![checkbox_border, checkbox_box, check, label]
} else {
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 3205fe55..da070f5c 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -5,6 +5,13 @@ use crate::Transformation;
use std::cell::RefCell;
use std::collections::HashMap;
+pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External {
+ name: "iced_wgpu icons",
+ bytes: include_bytes!("text/icons.ttf"),
+};
+
+pub const CHECKMARK_ICON: char = '\u{F00C}';
+
pub struct Pipeline {
draw_brush: RefCell<wgpu_glyph::GlyphBrush<'static, ()>>,
draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,
@@ -91,10 +98,10 @@ impl Pipeline {
// TODO: This is a bit hacky. We are loading the debug font as the
// first font in the `draw_brush`. The `measure_brush` does not
- // contain this font.
+ // contain this, hence we subtract 1.
//
- // This should go away once we improve the debug view and integrate
- // it as just another UI app.
+ // This should go away once we unify `draw_brush` and
+ // `measure_brush`.
font_id: wgpu_glyph::FontId(font_id - 1),
..Default::default()
};
diff --git a/wgpu/src/text/icons.ttf b/wgpu/src/text/icons.ttf
new file mode 100644
index 00000000..1c832f86
--- /dev/null
+++ b/wgpu/src/text/icons.ttf
Binary files differ