Skip to content
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
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ ui-events = {version = "0.2", features = ["kurbo"]}
ui-events-winit = {path = "./ui-events-winit", package = "ui-events-floem-winit"}
dpi = { version = "0.1.2", default-features = false }

dioxus-devtools = "=0.7.0-rc.3"

[dependencies]
slotmap = "1.0"
sha2 = "0.10"
Expand Down Expand Up @@ -94,6 +96,11 @@ fluent-bundle = { version = "0.16", optional = true }
unic-langid = { version = "0.9", optional = true }
sys-locale = {version = "0.3.2", optional = true }

#hotpatching
tungstenite = { version = "0.26.2", optional = true }
serde_json = { version = "1.0.140", optional = true }
dioxus-devtools = { workspace = true, optional = true}

[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
muda = { workspace = true }

Expand Down Expand Up @@ -121,6 +128,7 @@ objc2-app-kit = { version = "0.3", features = [
default = ["editor", "default-image-formats", "vger"]
vello = ["dep:floem_vello_renderer"]
vger = ["dep:floem_vger_renderer"]
hotpatch = ["dep:tungstenite", "dep:serde_json", "dep:dioxus-devtools", "floem_reactive/hotpatch"]
serde = [
"dep:serde",
"winit/serde",
Expand Down Expand Up @@ -159,3 +167,6 @@ rfd-async-std = ["dep:rfd", "rfd/async-std"]
rfd-tokio = ["dep:rfd", "rfd/tokio"]
crossbeam = ["dep:crossbeam", "floem_renderer/crossbeam"]
localization = ["dep:fluent-bundle", "dep:unic-langid", "dep:sys-locale"]



3 changes: 3 additions & 0 deletions examples/localization/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ version.workspace = true

[dependencies]
floem = { path = "../..", features = ["vello", "localization"] }

[features]
hotpatch = ["floem/hotpatch"]
34 changes: 33 additions & 1 deletion examples/localization/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use floem::{action::inspect, prelude::*};
use floem::{action::{inspect, set_window_scale}, prelude::*};
use localization::*;

fn main() {
Expand Down Expand Up @@ -55,6 +55,8 @@ fn counter_view() -> impl IntoView {
))
.style(|s| s.size_full().items_center().justify_center().gap(10.));

let mut window_scale = RwSignal::new(1.);

(lang_tabs, value_controls)
.v_stack()
.style(move |s| {
Expand All @@ -75,4 +77,34 @@ fn counter_view() -> impl IntoView {
inspect();
},
)
.on_key_down(
floem::keyboard::Key::Character("=".into()),
|m| m.meta(),
move |_| {
set_window_scale({
window_scale.update(|s| *s *= 1.1);
window_scale.get_untracked()
});
},
)
.on_key_down(
floem::keyboard::Key::Character("-".into()),
|m| m.meta(),
move |_| {
set_window_scale({
window_scale.update(|s| *s /= 1.1);
window_scale.get_untracked()
});
},
)
.on_key_down(
floem::keyboard::Key::Character("0".into()),
|m| m.meta(),
move |_| {
set_window_scale({
window_scale.update(|s| *s = 1.);
window_scale.get_untracked()
});
},
)
}
1 change: 1 addition & 0 deletions examples/widget-gallery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ stacks = { path = "../stacks/", optional = true }
default = ["full"]
vello = ["floem/vello"]
full = ["dep:files", "dep:stacks"]
hotpatch = ["floem/hotpatch"]
36 changes: 4 additions & 32 deletions examples/widget-gallery/src/buttons.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
use floem::{
peniko::{color::palette, Color},
prelude::{
palette::css::{DARK_GRAY, WHITE_SMOKE},
RwSignal, SignalGet,
},
style::CursorStyle,
theme::StyleThemeExt,
views::{button, toggle_button, Decorators, ToggleButton, ToggleHandleBehavior},
views::{button, toggle_button, Decorators, ToggleHandleBehavior},
IntoView,
};

use crate::form::{form, form_item};

pub fn button_view() -> impl IntoView {
let state = RwSignal::new(false);
form((
form_item(
"Basic Button:",
Expand All @@ -27,8 +21,8 @@ pub fn button_view() -> impl IntoView {
s.border(1.0)
.border_radius(10.0)
.padding(10.0)
.background(palette::css::YELLOW_GREEN)
.color(palette::css::DARK_GREEN)
.background(palette::css::RED)
.color(palette::css::BLACK.with_alpha(0.5))
.cursor(CursorStyle::Pointer)
.active(|s| s.color(palette::css::WHITE).background(palette::css::RED))
.hover(|s| s.background(Color::from_rgb8(244, 67, 54)))
Expand All @@ -48,34 +42,12 @@ pub fn button_view() -> impl IntoView {
}),
),
form_item(
"Toggle button - Snap:",
toggle_button(|| true)
.on_toggle(|_| {
println!("Button Toggled");
})
.toggle_style(|s| s.behavior(ToggleHandleBehavior::Snap)),
),
form_item(
"Toggle button - Follow:",
"Toggle button",
toggle_button(|| true)
.on_toggle(|_| {
println!("Button Toggled");
})
.toggle_style(|s| s.behavior(ToggleHandleBehavior::Follow)),
),
form_item(
"Toggle button - toggle background:",
ToggleButton::new_rw(state).toggle_style(move |s| {
s.apply_if(state.get(), |s| {
s.accent_color(DARK_GRAY).handle_color(WHITE_SMOKE)
})
.behavior(ToggleHandleBehavior::Snap)
}),
),
))
.style(move |s| {
s.apply_if(state.get(), |s| {
s.with_theme(|s, t| s.background(t.bg_elevated()))
})
})
}
1 change: 0 additions & 1 deletion examples/widget-gallery/src/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pub const CROSS_SVG: &str = r##"
"##;

pub fn checkbox_view() -> impl IntoView {
// let width = 160.0;
let is_checked = RwSignal::new(true);
form((
form_item("Checkbox:", Checkbox::new_rw(is_checked)),
Expand Down
2 changes: 1 addition & 1 deletion examples/widget-gallery/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub mod tabs;
pub mod texteditor;

use floem::{
action::{add_overlay, set_window_menu, toggle_theme},
action::{add_overlay, set_window_menu, set_window_scale, toggle_theme},
event::{Event, EventListener},
kurbo::Size,
menu::*,
Expand Down
13 changes: 4 additions & 9 deletions examples/widget-gallery/src/radio_buttons.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::Display;

use floem::{prelude::*, style_class};
use floem::prelude::*;
use strum::IntoEnumIterator;

use crate::form::{form, form_item};
Expand All @@ -22,10 +22,7 @@ impl Display for OperatingSystem {
}
}

style_class!(RadioButtonGroupClass);

pub fn radio_buttons_view() -> impl IntoView {
// let width = 160.0;
let operating_system = RwSignal::new(OperatingSystem::Windows);
form((
form_item(
Expand All @@ -48,8 +45,7 @@ pub fn radio_buttons_view() -> impl IntoView {
"Labelled Radio Buttons:",
OperatingSystem::iter()
.map(move |os| RadioButton::new_labeled_rw(os, operating_system, move || os))
.v_stack()
.class(RadioButtonGroupClass),
.v_stack(),
),
form_item(
"Disabled Labelled Radio Buttons:",
Expand All @@ -58,9 +54,8 @@ pub fn radio_buttons_view() -> impl IntoView {
RadioButton::new_labeled_get(os, operating_system, move || os)
.style(|s| s.set_disabled(true))
})
.v_stack()
.class(RadioButtonGroupClass),
.v_stack(),
),
))
.style(|s| s.class(RadioButtonGroupClass, |s| s.gap(10.).margin_left(5.)))
.style(|s| s.class(RadioButtonGroupClass, |s| s.gap(10.)))
}
4 changes: 4 additions & 0 deletions reactive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ license.workspace = true

[dependencies]
smallvec = "1.10.0"
dioxus-devtools = {workspace = true, optional = true}

[features]
hotpatch = ["dep:dioxus-devtools"]
124 changes: 124 additions & 0 deletions reactive/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub(crate) trait EffectTrait {
fn run(&self) -> bool;
fn add_observer(&self, id: Id);
fn clear_observers(&self) -> HashSet<Id>;
#[allow(dead_code)]
fn hot_fn_ptr(&self) -> u64;
}

struct Effect<T, F>
Expand Down Expand Up @@ -249,6 +251,11 @@ where
fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}

fn hot_fn_ptr(&self) -> u64 {
std::ptr::addr_of!(self.f) as *const () as u64
// HotFn::current(&self.f).ptr_address().0
}
}

impl<T, I, C, U> EffectTrait for UpdaterEffect<T, I, C, U>
Expand Down Expand Up @@ -279,6 +286,10 @@ where
fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}
fn hot_fn_ptr(&self) -> u64 {
std::ptr::addr_of!(self.compute) as *const () as u64
// HotFn::current(&self.compute).ptr_address().0
}
}

pub struct SignalTracker {
Expand Down Expand Up @@ -357,4 +368,117 @@ impl EffectTrait for TrackingEffect {
fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}
fn hot_fn_ptr(&self) -> u64 {
let rc_ptr = Rc::as_ptr(&self.on_change);
rc_ptr as *const () as u64
// HotFn::current(&*self.on_change).ptr_address().0
}
}
#[cfg(feature = "hotpatch")]
pub use hotpatch::*;

#[cfg(feature = "hotpatch")]
mod hotpatch {
use std::{cell::RefCell, collections::HashSet, marker::PhantomData, mem, rc::Rc};

use dioxus_devtools::subsecond::{HotFn, HotFunction};

use super::*;

struct HotUpdaterEffect<R, C, U, M, T>
where
C: dioxus_devtools::subsecond::HotFunction<T, M, Return = R>,
U: Fn(R),
{
id: Id,
compute: RefCell<HotFn<T, M, C>>,
on_change: U,
observers: RefCell<HashSet<Id>>,
phantom: PhantomData<M>,
}
impl<R, C, U, M, T> Drop for HotUpdaterEffect<R, C, U, M, T>
where
C: dioxus_devtools::subsecond::HotFunction<T, M, Return = R>,
U: Fn(R),
{
fn drop(&mut self) {
self.id.dispose();
}
}
/// Create an effect updater that runs `on_change` when any signals that subscribe during the
/// run of `compute` are updated. `compute` is immediately run only once, and its value is returned
/// from the call to `create_updater`.
pub fn create_hot_updater<T, R, C, M: 'static>(
compute: HotFn<T, M, C>,
on_change: impl Fn(R) + 'static,
) -> R
where
R: 'static,
C: dioxus_devtools::subsecond::HotFunction<T, M, Return = R> + 'static,
T: std::default::Default + 'static, // C: HotFunction<(), M, Return = R> + 'static,
{
let id = Id::next();
let effect = Rc::new(HotUpdaterEffect {
id,
compute: RefCell::new(compute),
on_change,
observers: RefCell::new(HashSet::default()),
phantom: PhantomData,
});
crate::runtime::register_effect(effect.clone());
id.set_scope();
run_initial_hot_updater_effect(effect)
}
impl<R, C, U, M, T: std::default::Default> EffectTrait for HotUpdaterEffect<R, C, U, M, T>
where
R: 'static,
C: HotFunction<T, M, Return = R>,
U: Fn(R),
{
fn id(&self) -> Id {
self.id
}

fn run(&self) -> bool {
let compute_fn = &self.compute;
let result = compute_fn.borrow_mut().call(T::default());
(self.on_change)(result);
true
}

fn add_observer(&self, id: Id) {
self.observers.borrow_mut().insert(id);
}

fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}

fn hot_fn_ptr(&self) -> u64 {
let compute_fn = &self.compute;
compute_fn.borrow().ptr_address().0
}
}
fn run_initial_hot_updater_effect<R, C, U, M: 'static, T: std::default::Default + 'static>(
effect: Rc<HotUpdaterEffect<R, C, U, M, T>>,
) -> R
where
R: 'static,
C: HotFunction<T, M, Return = R> + 'static,
U: Fn(R) + 'static,
{
let effect_id = effect.id();
let result = RUNTIME.with(|runtime| {
*runtime.current_effect.borrow_mut() = Some(effect.clone());
let effect_scope = Scope(effect_id, PhantomData);
let result = with_scope(effect_scope, || {
effect_scope.track();
let compute_fn = &effect.compute;
compute_fn.borrow_mut().call(T::default())
});
*runtime.current_effect.borrow_mut() = None;
result
});
result
}
}
Loading
Loading