diff options
Diffstat (limited to '')
| -rw-r--r-- | examples/todos.rs | 160 | ||||
| -rw-r--r-- | wgpu/src/renderer.rs | 18 | ||||
| -rw-r--r-- | wgpu/src/renderer/widget/checkbox.rs | 17 | ||||
| -rw-r--r-- | wgpu/src/text.rs | 13 | ||||
| -rw-r--r-- | wgpu/src/text/icons.ttf | bin | 0 -> 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.ttfBinary files differ new file mode 100644 index 00000000..1c832f86 --- /dev/null +++ b/wgpu/src/text/icons.ttf | 
