Skip to content

Commit

Permalink
Deleted SignalClone.
Browse files Browse the repository at this point in the history
create_derived now always returns a signal.
  • Loading branch information
viridia committed Feb 7, 2024
1 parent 016e621 commit 99f26a8
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 144 deletions.
17 changes: 5 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,8 @@ Mutables are transactional: when you write to a mutable, the change does not tak
the next frame. There's an ECS system that "commits" the pending changes.

The `.get()`, `.set()` and `.signal()` methods given above assume that the data in the mutable
implements `Copy`. There is another set of methods for data that implements `Clone`:

* Getting the data via `mutable.get_clone(context)`;
* Setting the data via `mutable.set_clone(context, value)`;
* Accessing the data via a signal: `mutable.signal_clone()` which returns a `SignalClone` object;

Other than the fact that they work with clones, the methods behave identically.
implements `Copy`. There is also a `.get_clone()` method, which works with data types that
implement `Clone`.

## Derived Signals

Expand All @@ -155,16 +150,14 @@ let panel_width = cx
.create_derived(|cx| {
let res = cx.use_resource::<PanelWidth>();
res.0
})
.signal();
});
```

The `.create_derived()` method returns a `Derived<T>`, which, like mutables, is just a handle
containing an entity id. It has methods like a mutable: `.get()`, `.map()`. Reading a derived
The `.create_derived()` method returns a `Signal<T>`. Reading a derived signal
adds all of the derived's dependencies to the current tracking scope, so if any of those
dependencies change, the caller of the derived will be re-run.

Deriveds are not memoized, however, for that we need to use `Memo` (still to be implemented).
Derived signals are not memoized, however, for that we need to use `Memo` (still to be implemented).

## Tracking Scopes and Reactions

Expand Down
2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
* Checkbox (finish)
* Radio
* Button Group
* DisclosureTriangle - with transition
* Flex
* Multi-layered nine-patch button.
* Modal
* Menu
* Focus Outlines
* etc...

## Notes on fine-grained
Expand Down
18 changes: 14 additions & 4 deletions crates/obsidian_ui/src/controls/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ pub enum ButtonVariant {

/// An appearance indicating a potentially dangerous action.
Danger,

/// A button that is in a "toggled" state.
Selected,
}

/// Button properties
#[derive(Default)]
pub struct ButtonProps<V: ViewTuple + Clone> {
/// Color variant - default, primary or danger.
pub variant: ButtonVariant,
pub variant: Signal<ButtonVariant>,

/// Button size.
pub size: Size,
Expand Down Expand Up @@ -73,6 +76,7 @@ fn style_button_bg(ss: &mut StyleBuilder) {
/// Construct a button widget.
pub fn button<V: ViewTuple + Clone>(cx: &mut Cx<ButtonProps<V>>) -> Element<NodeBundle> {
let id = cx.create_entity();
let variant = cx.props.variant;
let pressed = cx.create_mutable::<bool>(false);
let hovering = cx.create_hover_signal(id);

Expand Down Expand Up @@ -146,10 +150,16 @@ pub fn button<V: ViewTuple + Clone>(cx: &mut Cx<ButtonProps<V>>) -> Element<Node
.create_effect(move |cx, _| {
let is_pressed = pressed.get(cx);
let is_hovering = hovering.get(cx);
let base_color = match variant.get(cx) {
ButtonVariant::Default => colors::U3,
ButtonVariant::Primary => colors::PRIMARY,
ButtonVariant::Danger => colors::DESTRUCTIVE,
ButtonVariant::Selected => colors::U4,
};
let color = match (is_pressed, is_hovering) {
(true, _) => colors::U3.lighter(0.05),
(false, true) => colors::U3.lighter(0.01),
(false, false) => colors::U3,
(true, _) => base_color.lighter(0.05),
(false, true) => base_color.lighter(0.01),
(false, false) => base_color,
};
let mut ui_materials = cx
.world_mut()
Expand Down
2 changes: 1 addition & 1 deletion crates/obsidian_ui/src/controls/gradient_slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub fn gradient_slider(cx: &mut Cx<GradientSliderProps>) -> Element<NodeBundle>
};

// This should really be an effect.
let color_stops: Derived<(usize, [Vec4; 8])> = {
let color_stops: Signal<(usize, [Vec4; 8])> = {
let gradient = cx.props.gradient.clone();
cx.create_derived(move |cc| {
gradient.map(cc, |g| {
Expand Down
179 changes: 179 additions & 0 deletions examples/complex/color_edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use bevy::{
ecs::system::Resource,
prelude::default,
ui::{self, node_bundles::NodeBundle},
};
use bevy_color::{Hsla, Srgba};
use bevy_reactor::*;
use obsidian_ui::controls::{
button, gradient_slider, ButtonProps, ButtonVariant, GradientSliderProps,
};

#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum ColorMode {
#[default]
Rgb,
Hsl,
Recent,
}

#[derive(Default, Resource)]
pub struct ColorEditState {
pub mode: ColorMode,
pub rgb: Srgba,
pub hsl: Hsla,
pub recent: Vec<Srgba>,
}

fn style_color_edit(sb: &mut StyleBuilder) {
sb.display(ui::Display::Flex)
.align_items(ui::AlignItems::Stretch)
.flex_direction(ui::FlexDirection::Column)
.gap(4);
}

fn style_mode_selector(sb: &mut StyleBuilder) {
sb.display(ui::Display::Flex)
.flex_direction(ui::FlexDirection::Row)
.justify_content(ui::JustifyContent::Center)
.align_items(ui::AlignItems::Center)
.gap(1);
}

fn style_sliders(sb: &mut StyleBuilder) {
sb.display(ui::Display::Grid)
.grid_template_columns(vec![
ui::RepeatedGridTrack::fr(1, 1.),
ui::RepeatedGridTrack::auto(1),
])
.grid_auto_flow(ui::GridAutoFlow::Row);
}

fn style_slider(ss: &mut StyleBuilder) {
ss.align_self(ui::AlignSelf::Stretch);
}

pub fn color_edit(cx: &mut Cx) -> impl View {
// let state = cx.use_resource::<ColorEditState>();

Element::<NodeBundle>::new()
.with_styles(style_color_edit)
.children((
Element::<NodeBundle>::new()
.with_styles(style_mode_selector)
.children((
button.bind(ButtonProps {
children: "RGB",
variant: cx.create_derived(|cx| {
match cx.use_resource::<ColorEditState>().mode {
ColorMode::Rgb => ButtonVariant::Selected,
_ => ButtonVariant::Default,
}
}),
on_click: Some(cx.create_callback(|cx: &mut Cx| {
cx.world_mut().resource_mut::<ColorEditState>().mode = ColorMode::Hsl;
})),
..default()
}),
button.bind(ButtonProps {
children: "HSL",
variant: cx.create_derived(|cx| {
match cx.use_resource::<ColorEditState>().mode {
ColorMode::Hsl => ButtonVariant::Selected,
_ => ButtonVariant::Default,
}
}),
on_click: Some(cx.create_callback(|cx: &mut Cx| {
cx.world_mut().resource_mut::<ColorEditState>().mode = ColorMode::Hsl;
})),
..default()
}),
button.bind(ButtonProps {
children: "Recent",
variant: cx.create_derived(|cx| {
match cx.use_resource::<ColorEditState>().mode {
ColorMode::Recent => ButtonVariant::Selected,
_ => ButtonVariant::Default,
}
}),
on_click: Some(cx.create_callback(|cx: &mut Cx| {
cx.world_mut().resource_mut::<ColorEditState>().mode =
ColorMode::Recent;
})),
..default()
}),
)),
Element::<NodeBundle>::new()
.with_styles(style_sliders)
.children((
gradient_slider.bind(GradientSliderProps {
gradient: cx.create_derived(|cx| {
let rgb = cx.use_resource::<ColorEditState>().rgb;
vec![
Srgba::new(0.0, rgb.green, rgb.blue, 1.0),
Srgba::new(1.0, rgb.green, rgb.blue, 1.0),
]
}),
min: Signal::Constant(0.),
max: Signal::Constant(255.),
value: cx.create_derived(|cx| cx.use_resource::<ColorEditState>().rgb.red),
style: StyleHandle::new(style_slider),
precision: 1,
on_change: Some(cx.create_callback(move |cx| {
cx.world_mut().resource_mut::<ColorEditState>().rgb.red = cx.props;
})),
..default()
}),
// gradient_slider.bind(GradientSliderProps {
// gradient: Signal::Constant(vec![
// Srgba::new(0.5, 0.5, 0.5, 1.0),
// Srgba::new(1.0, 0.0, 0.0, 1.0),
// ]),
// min: Signal::Constant(0.),
// max: Signal::Constant(255.),
// value: saturation.signal(),
// style: StyleHandle::new(style_slider),
// precision: 1,
// on_change: Some(cx.create_callback(move |cx| {
// saturation.set(cx, cx.props);
// })),
// ..default()
// }),
// gradient_slider.bind(GradientSliderProps {
// gradient: Signal::Constant(vec![
// Srgba::from(Hsla::new(0.0, 1.0, 0.5, 1.0)),
// Srgba::from(Hsla::new(60.0, 1.0, 0.5, 1.0)),
// Srgba::from(Hsla::new(120.0, 1.0, 0.5, 1.0)),
// Srgba::from(Hsla::new(180.0, 1.0, 0.5, 1.0)),
// Srgba::from(Hsla::new(240.0, 1.0, 0.5, 1.0)),
// Srgba::from(Hsla::new(300.0, 1.0, 0.5, 1.0)),
// Srgba::from(Hsla::new(360.0, 1.0, 0.5, 1.0)),
// ]),
// min: Signal::Constant(0.),
// max: Signal::Constant(255.),
// value: saturation.signal(),
// style: StyleHandle::new(style_slider),
// precision: 1,
// on_change: Some(cx.create_callback(move |cx| {
// saturation.set(cx, cx.props);
// })),
// ..default()
// }),
// gradient_slider.bind(GradientSliderProps {
// gradient: Signal::Constant(vec![
// Srgba::new(0.5, 0.5, 0.5, 0.0),
// Srgba::new(0.5, 0.5, 0.5, 1.5),
// ]),
// min: Signal::Constant(0.),
// max: Signal::Constant(255.),
// value: saturation.signal(),
// style: StyleHandle::new(style_slider),
// precision: 1,
// on_change: Some(cx.create_callback(move |cx| {
// saturation.set(cx, cx.props);
// })),
// ..default()
// }),
)),
))
}
24 changes: 16 additions & 8 deletions examples/complex/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Example of a simple UI layout
mod color_edit;

use bevy_color::{Hsla, Srgba};
use bevy_mod_picking::{
backends::bevy_ui::BevyUiBackend,
input::InputPlugin,
picking_core::{CorePlugin, InteractionPlugin},
};
use color_edit::{color_edit, ColorEditState, ColorMode};
use obsidian_ui::{
colors,
controls::{
Expand Down Expand Up @@ -119,6 +122,12 @@ fn main() {
)
.init_resource::<Counter>()
.insert_resource(PanelWidth(200.))
.insert_resource(ColorEditState {
mode: ColorMode::Rgb,
rgb: Srgba::new(255., 0., 0., 255.),
recent: vec![],
..default()
})
.init_resource::<viewport::ViewportInset>()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_plugins((CorePlugin, InputPlugin, InteractionPlugin, BevyUiBackend))
Expand Down Expand Up @@ -170,12 +179,10 @@ fn ui_main(cx: &mut Cx) -> impl View {
Srgba::new(red / 255., saturation / 100.0, 0.0, 1.0)
});

let panel_width = cx
.create_derived(|cx| {
let res = cx.use_resource::<PanelWidth>();
res.0
})
.signal();
let panel_width = cx.create_derived(|cx| {
let res = cx.use_resource::<PanelWidth>();
res.0
});

Element::<NodeBundle>::new()
.with_styles((typography::text_default, style_main))
Expand Down Expand Up @@ -205,6 +212,7 @@ fn ui_main(cx: &mut Cx) -> impl View {
..default()
}),
)),
color_edit.bind(()),
Element::<NodeBundle>::new()
.with_styles(style_color_edit)
.children((
Expand Down Expand Up @@ -310,13 +318,13 @@ fn ui_main(cx: &mut Cx) -> impl View {
..default()
}),
swatch.bind(SwatchProps {
color: rgb_color.signal(),
color: rgb_color,
size: Size::Md,
// style: StyleHandle::new(style_slider),
..default()
}),
text_computed(move |cx| {
let color = rgb_color.signal().get(cx);
let color = rgb_color.get(cx);
color.to_hex()
}),
)),
Expand Down
8 changes: 4 additions & 4 deletions src/cx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
derived::{Derived, DerivedCell, ReadDerived, ReadDerivedInternal},
mutable::{MutableCell, MutableNextCell, ReadMutable, WriteMutable},
tracking_scope::TrackingScope,
Mutable,
Mutable, Signal,
};

/// An immutable reactive context, used for reactive closures such as derived signals.
Expand Down Expand Up @@ -131,16 +131,16 @@ pub trait RunContextSetup<'p> {
fn create_derived<R: 'static, F: Send + Sync + 'static + Fn(&mut Rcx) -> R>(
&mut self,
callback: F,
) -> Derived<R> {
) -> Signal<R> {
let derived = self
.world_mut()
.spawn(DerivedCell::<R>(Arc::new(callback)))
.id();
self.add_owned(derived);
Derived {
Signal::Derived(Derived {
id: derived,
marker: PhantomData,
}
})
}
}

Expand Down
Loading

0 comments on commit 99f26a8

Please sign in to comment.