Skip to content

Commit

Permalink
Merge pull request #30 from fwcd/slider
Browse files Browse the repository at this point in the history
Add slider
  • Loading branch information
fwcd authored Sep 8, 2024
2 parents afaf869 + 2984f02 commit 0f282e0
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 3 deletions.
48 changes: 48 additions & 0 deletions examples/sliders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#![feature(type_alias_impl_trait, impl_trait_in_assoc_type)]

use nuit::{prelude::*, Circle, Font, FontDesign, FontSize, Frame, HStack, Slider, Text, VStack, Vec2};

#[derive(Bind, Default)]
struct SlidersView {
position: State<Vec2<f64>>,
}

impl View for SlidersView {
type Body = impl View;

#[allow(clippy::cast_possible_truncation)]
fn body(&self) -> Self::Body {
let position = self.position.clone();
let width = 400.0;
let height = 300.0;
let slider_width = 100.0;

VStack::from((
Circle::new()
.frame(10)
.offset(position.get())
.frame((width, height)),

HStack::from((
Text::new(format!("X: {:>4}", position.get().x as i32)),
Slider::with_default_step(
position.project(|p| &mut p.x),
-(width / 2.0)..=(width / 2.0)
)
.frame(Frame::with_width(slider_width)),

Text::new(format!("Y: {:>4}", position.get().y as i32)),
Slider::with_default_step(
position.project(|p| &mut p.y),
-(height / 2.0)..=(height / 2.0)
)
.frame(Frame::with_width(slider_width)),
)
.font(Font::system(FontSize::BODY, FontDesign::Monospaced, None))),
))
}
}

fn main() {
nuit::run_app(SlidersView::default());
}
14 changes: 13 additions & 1 deletion nuit-bridge-adwaita/src/node_widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod imp;

use std::rc::Rc;

use adw::{glib::{self, Object}, gtk::{self, Align, Button, Label, Orientation, Text}, prelude::{BoxExt, ButtonExt, EditableExt, WidgetExt}, subclass::prelude::*};
use adw::{glib::{self, Object}, gtk::{self, Align, Button, Label, Orientation, Scale, Text}, prelude::{BoxExt, ButtonExt, EditableExt, RangeExt, WidgetExt}, subclass::prelude::*};
use nuit_core::{clone, Event, Id, IdPath, IdPathBuf, Identified, Node};

use crate::convert::ToGtk;
Expand Down Expand Up @@ -105,6 +105,18 @@ impl NodeWidget {
}
self.append(&button);
},
Node::Slider { value, lower_bound, upper_bound, step } => {
let scale = Scale::with_range(Orientation::Horizontal, *lower_bound, *upper_bound, step.unwrap_or(1e-32));
scale.set_value(*value);
scale.set_width_request(150);
if let Some(ref fire_event) = *fire_event {
scale.connect_value_changed(clone!(fire_event, id_path => move |scale| {
let value = scale.value();
fire_event(&id_path, &Event::UpdateSliderValue { value });
}));
}
self.append(&scale);
},
Node::HStack { spacing, alignment, wrapped } => {
let gtk_box = gtk::Box::new(Orientation::Horizontal, *spacing as i32);
gtk_box.set_valign(alignment.to_gtk());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ enum Event: Codable, Hashable {
case gesture(gesture: GestureEvent)
case updateText(content: String)
case updatePickerSelection(id: Id)
case updateSliderValue(value: Double)
}
1 change: 1 addition & 0 deletions nuit-bridge-swiftui/Sources/NuitBridgeSwiftUI/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ indirect enum Node: Codable, Hashable {
case textField(content: String)
case button(label: Identified<Node>)
case picker(title: String, selection: Id, content: Identified<Node>)
case slider(value: Double, lowerBound: Double, upperBound: Double, step: Double?)

// MARK: Aggregation
case child(wrapped: Identified<Node>)
Expand Down
10 changes: 10 additions & 0 deletions nuit-bridge-swiftui/Sources/NuitBridgeSwiftUI/NodeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ struct NodeView: View {
)) {
NodeView(node: content.value, idPath: idPath + [content.id])
}
case let .slider(value: value, lowerBound: lowerBound, upperBound: upperBound, step: step):
let binding = Binding(
get: { value },
set: { root.fire(event: .updateSliderValue(value: $0), for: idPath) }
)
if let step {
Slider(value: binding, in: lowerBound...upperBound, step: step)
} else {
Slider(value: binding, in: lowerBound...upperBound)
}

// MARK: Aggregation
case let .child(wrapped: wrapped):
Expand Down
2 changes: 2 additions & 0 deletions nuit-core/src/compose/view/widget/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod button;
mod picker;
mod slider;
mod text_field;
mod text;

pub use button::*;
pub use picker::*;
pub use slider::*;
pub use text_field::*;
pub use text::*;
43 changes: 43 additions & 0 deletions nuit-core/src/compose/view/widget/slider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::ops::RangeInclusive;

use nuit_derive::Bind;

use crate::{Access, Binding, Context, Event, IdPath, Node, View};

/// A control for selecting numeric values from a bounded range.
#[derive(Debug, Clone, Bind)]
pub struct Slider {
value: Binding<f64>,
range: RangeInclusive<f64>,
step: Option<f64>,
}

impl Slider {
#[must_use]
pub const fn new(value: Binding<f64>, range: RangeInclusive<f64>, step: Option<f64>) -> Self {
Self { value, range, step }
}

#[must_use]
pub const fn with_default_step(value: Binding<f64>, range: RangeInclusive<f64>) -> Self {
Self { value, range, step: None }
}
}

impl View for Slider {
fn fire(&self, event: &Event, event_path: &IdPath, _context: &Context) {
assert!(event_path.is_root());
if let Event::UpdateSliderValue { value } = event {
self.value.set(*value);
}
}

fn render(&self, _context: &Context) -> Node {
Node::Slider {
value: self.value.get(),
lower_bound: *self.range.start(),
upper_bound: *self.range.end(),
step: self.step,
}
}
}
1 change: 1 addition & 0 deletions nuit-core/src/event/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub enum Event {
Gesture { gesture: GestureEvent },
UpdateText { content: String },
UpdatePickerSelection { id: Id },
UpdateSliderValue { value: f64 },

// Lifecycle
Appear,
Expand Down
1 change: 1 addition & 0 deletions nuit-core/src/node/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum Node {
TextField { content: String },
Button { label: Box<Identified<Node>> },
Picker { title: String, selection: Id, content: Box<Identified<Node>> },
Slider { value: f64, lower_bound: f64, upper_bound: f64, step: Option<f64> },

// Aggregation
Child { wrapped: Box<Identified<Node>> },
Expand Down
16 changes: 14 additions & 2 deletions nuit-core/src/utils/font/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ impl Font {
pub const FOOTNOTE: Self = Self::with_level(FontLevel::Footnote);

#[must_use]
pub fn system(size: impl Into<FontSize>, design: Option<FontDesign>, weight: Option<FontWeight>) -> Self {
Self::System { size: size.into(), design, weight }
pub fn system(size: impl Into<FontSize>, design: impl Into<Option<FontDesign>>, weight: impl Into<Option<FontWeight>>) -> Self {
Self::System { size: size.into(), design: design.into(), weight: weight.into() }
}

#[must_use]
Expand All @@ -45,3 +45,15 @@ impl Font {
Self::System { size: FontSize::level(level), design: None, weight: None }
}
}

impl From<FontSize> for Font {
fn from(size: FontSize) -> Self {
Self::with_size(size)
}
}

impl From<FontLevel> for Font {
fn from(level: FontLevel) -> Self {
Self::with_level(level)
}
}

0 comments on commit 0f282e0

Please sign in to comment.