Skip to content

Commit

Permalink
Start implementing copy to all (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zerthox committed Jan 5, 2025
1 parent 36f6512 commit 1f711d4
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 49 deletions.
124 changes: 124 additions & 0 deletions reffect/src/action/dynamic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#![allow(unused)]

use crate::render_util::item_context_menu;
use nexus::imgui::{MenuItem, Ui};
use std::fmt;

pub struct DynAction<T>(Option<Box<dyn FnMut(&mut T)>>);

impl<T> DynAction<T> {
pub const fn empty() -> Self {
Self(None)
}

pub fn new(action: impl FnMut(&mut T) + 'static) -> Self {
Self(Some(Box::new(action)))
}

pub fn try_new(action: Option<impl FnMut(&mut T) + 'static>) -> Self {
match action {
Some(action) => Self::new(action),
None => Self::empty(),
}
}

pub fn is_none(&self) -> bool {
!self.is_some()
}

pub fn is_some(&self) -> bool {
self.0.is_some()
}

pub fn perform(&mut self, value: &mut T) {
if let Some(action) = self.0.as_mut() {
action(value)
}
}

pub fn set(&mut self, action: impl FnMut(&mut T) + 'static) {
*self = Self::new(action);
}

pub fn map<O>(self, mut map: impl (FnMut(&mut O) -> &mut T) + 'static) -> DynAction<O>
where
T: 'static,
{
DynAction::try_new(
self.0
.map(|mut action| move |value: &mut O| action(map(value))),
)
}

pub fn try_map<O>(
self,
mut try_map: impl FnMut(&mut O) -> Option<&mut T> + 'static,
) -> DynAction<O>
where
T: 'static,
{
DynAction::try_new(self.0.map(|mut action| {
move |value: &mut O| {
if let Some(inner) = try_map(value) {
action(inner)
}
}
}))
}

pub fn or(&mut self, other: Self) {
if self.is_none() {
*self = other;
}
}

pub fn render_copy_all(
&mut self,
ui: &Ui,
id: impl Into<String>,
action: impl FnMut(&mut T) + 'static,
) {
item_context_menu(id, || {
if MenuItem::new("Copy to all siblings").build(ui) {
self.set(action);
}
});
}
}

impl<T> fmt::Debug for DynAction<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("DynAction")
.field(&format_args!(
"{:?}",
self.0.as_ref().map(|inner| inner.as_ref() as *const _)
))
.finish()
}
}

/// Render a copy all context menu for the last item.
///
/// Note: this uses the field name as popup id!
#[macro_export]
macro_rules! render_copy_field {
($action:expr, $ui:expr, $self:ident . $field:ident) => {{
let value = $self.$field;
$action.render_copy_all($ui, stringify!($field), move |other| {
other.$field = value;
});
}};
($action:expr, $ui:expr, *$field:ident) => {{
let value = *$field;
$action.render_copy_all($ui, stringify!($field), move |other| {
other.$field = value;
});
}};
($action:expr, $ui:expr, $field:ident) => {{
$action.render_copy_all($ui, stringify!($field), move |other| {
other.$field = $field;
});
}};
}

pub use render_copy_field;
3 changes: 2 additions & 1 deletion reffect/src/action/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
mod dynamic;
mod element;

use crate::render_util::{button_size_with_spacing, close_button};
use nexus::imgui::{Direction, Ui};

pub use self::element::*;
pub use self::{dynamic::*, element::*};

// TODO: action clear entire vec?

Expand Down
2 changes: 2 additions & 0 deletions reffect/src/elements/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ impl Default for Animation {
)]
pub enum AnimationKind {
Pulse,
// TODO: shake
// TODO: bounce
}

impl AnimationKind {
Expand Down
26 changes: 20 additions & 6 deletions reffect/src/elements/icon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ pub use self::{element::*, props::*, source::*};

use super::{align::AlignHorizontal, Props, RenderState};
use crate::{
action::DynAction,
context::Context,
render::{
colors::{self, with_alpha, with_alpha_factor},
ComponentWise, RenderDebug, RenderOptions,
},
render_copy_field,
render_util::{debug_optional, draw_spinner_bg, draw_text_bg, Rect},
settings::icon::{DurationBarSettings, DurationTextSettings, StackTextSettings},
trigger::{ProgressActive, ProgressValue, Skill},
Expand Down Expand Up @@ -197,24 +199,36 @@ impl Icon {
}
}
}
}

impl RenderOptions for Icon {
fn render_options(&mut self, ui: &Ui, ctx: &Context) {
pub fn render_options(&mut self, ui: &Ui, ctx: &Context) -> DynAction<Self> {
let mut action = DynAction::<Self>::empty();

self.source.render_select(ui, ctx);

ui.spacing();

self.props.base.render_options(ui, ctx);
let props_action = self.props.base.render_options(ui, ctx);
action.or(props_action.map(|icon: &mut Self| &mut icon.props.base));

ui.checkbox("Show Duration Bar", &mut self.duration_bar);
render_copy_field!(action, ui, self.duration_bar);

ui.checkbox("Show Duration Text", &mut self.duration_text);
render_copy_field!(action, ui, self.duration_text);

ui.checkbox("Show Stacks", &mut self.stacks_text);
render_copy_field!(action, ui, self.stacks_text);

action
}

fn render_tabs(&mut self, ui: &Ui, ctx: &Context) {
pub fn render_tabs(&mut self, ui: &Ui, ctx: &Context) -> DynAction<Self> {
if let Some(_token) = ui.tab_item("Condition") {
self.props.render_condition_options(ui, ctx);
self.props
.render_condition_options(ui, ctx)
.map(|icon: &mut Self| &mut icon.props)
} else {
DynAction::empty()
}
}
}
Expand Down
16 changes: 14 additions & 2 deletions reffect/src/elements/icon/props.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::{
action::DynAction,
context::Context,
elements::PartialProps,
render::{colors, RenderOptions},
render_copy_field,
render_util::{
helper, input_color_alpha, input_optional, input_percent_inverse,
input_positive_with_format, slider_percent_capped,
Expand Down Expand Up @@ -36,8 +38,8 @@ impl Default for IconProps {
}
}

impl RenderOptions for IconProps {
fn render_options(&mut self, ui: &Ui, _ctx: &Context) {
impl RenderOptions<DynAction<IconProps>> for IconProps {
fn render_options(&mut self, ui: &Ui, _ctx: &Context) -> DynAction<Self> {
let Self {
tint,
zoom,
Expand All @@ -46,12 +48,17 @@ impl RenderOptions for IconProps {
border_color,
} = self;

let mut action = DynAction::<Self>::empty();

input_color_alpha(ui, "Tint", tint);
render_copy_field!(action, ui, *tint);

input_percent_inverse("Zoom", zoom);
helper(ui, || ui.text("Icon zoom in percent"));
render_copy_field!(action, ui, *zoom);

slider_percent_capped(ui, "Round", round, 50.0);
render_copy_field!(action, ui, *round);
helper(ui, || ui.text("Corner rounding in percent"));

input_positive_with_format(
Expand All @@ -62,7 +69,12 @@ impl RenderOptions for IconProps {
"%.1f",
InputTextFlags::empty(),
);
render_copy_field!(action, ui, *border_size);

input_color_alpha(ui, "Border color", border_color);
render_copy_field!(action, ui, *border_color);

action
}
}

Expand Down
8 changes: 5 additions & 3 deletions reffect/src/elements/list/icon.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
action::DynAction,
context::Context,
elements::{Common, Element, ElementType, Icon, IconElement, RenderState},
render::{RenderDebug, RenderOptions},
Expand Down Expand Up @@ -69,8 +70,8 @@ impl ListIcon {
}
}

impl RenderOptions for ListIcon {
fn render_options(&mut self, ui: &Ui, ctx: &Context) {
impl RenderOptions<DynAction<ListIcon>> for ListIcon {
fn render_options(&mut self, ui: &Ui, ctx: &Context) -> DynAction<Self> {
ui.checkbox("Enabled", &mut self.enabled);
ui.input_text("Name", &mut self.name).build();

Expand All @@ -80,7 +81,8 @@ impl RenderOptions for ListIcon {

ui.spacing();

self.icon.render_options(ui, ctx);
let icon_action = self.icon.render_options(ui, ctx);
icon_action.map(|list_icon: &mut Self| &mut list_icon.icon)
}
}

Expand Down
32 changes: 22 additions & 10 deletions reffect/src/elements/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use self::{action::*, icon::*, layout::*};

use super::{Direction, RenderState};
use crate::{
action::DynAction,
context::Context,
render::{colors, Bounds, Render, RenderDebug, RenderOptions},
render_util::{
Expand Down Expand Up @@ -97,12 +98,13 @@ impl RenderOptions for IconList {
ui.text_disabled("Icons");

let mut action = IconAction::new();
for (i, icon) in self.icons.iter_mut().enumerate() {
let mut copy_action = DynAction::<ListIcon>::empty();
for (i, list_icon) in self.icons.iter_mut().enumerate() {
let _id = ui.push_id(i as i32);

let mut remains = true;
let style = style_disabled_if(ui, !icon.enabled);
let open = CollapsingHeader::new(format!("{}###icon{i}", icon.name))
let style = style_disabled_if(ui, !list_icon.enabled);
let open = CollapsingHeader::new(format!("{}###icon{i}", list_icon.name))
.flags(TreeNodeFlags::ALLOW_ITEM_OVERLAP)
.begin_with_close_button(ui, &mut remains);

Expand All @@ -121,7 +123,9 @@ impl RenderOptions for IconList {
action = IconAction::Cut(i);
}
if MenuItem::new("Copy").build(ui) {
ctx.edit.clipboard.set(icon.clone().into_element(self.size))
ctx.edit
.clipboard
.set(list_icon.clone().into_element(self.size))
}
if MenuItem::new("Duplicate").build(ui) {
action = IconAction::Duplicate(i);
Expand Down Expand Up @@ -155,15 +159,14 @@ impl RenderOptions for IconList {
ui.open_popup(&title);
}
if delete_confirm_modal(ui, &title, || {
ui.text(format!("Delete Icon {}?", icon.name))
ui.text(format!("Delete Icon {}?", list_icon.name))
}) {
action = IconAction::Delete(i);
}

drop(style);
if open {
// TODO: apply option to all context menu option
icon.render_options(ui, ctx);
copy_action.or(list_icon.render_options(ui, ctx));
ui.spacing();
}
}
Expand All @@ -180,22 +183,31 @@ impl RenderOptions for IconList {
});

action.perform(&mut self.icons, self.size, &ctx.edit);
for icon in &mut self.icons {
copy_action.perform(icon);
}
}

fn render_tabs(&mut self, ui: &Ui, ctx: &Context) {
if let Some(_token) = ui.tab_item("Condition") {
const INDENT: f32 = 10.0;
for (i, icon) in self.icons.iter_mut().enumerate() {
let mut action = DynAction::empty();

for (i, list_icon) in self.icons.iter_mut().enumerate() {
let _id = ui.push_id(i as i32);
let open = CollapsingHeader::new(format!("{}###icon{i}", icon.name))
let open = CollapsingHeader::new(format!("{}###icon{i}", list_icon.name))
.flags(TreeNodeFlags::ALLOW_ITEM_OVERLAP)
.begin(ui);
if open {
ui.indent_by(INDENT);
icon.icon.props.render_condition_options(ui, ctx);
action.or(list_icon.icon.props.render_condition_options(ui, ctx));
ui.unindent_by(INDENT);
}
}

for list_icon in &mut self.icons {
action.perform(&mut list_icon.icon.props);
}
}
}
}
Expand Down
Loading

0 comments on commit 1f711d4

Please sign in to comment.