collapsable rule editors

This commit is contained in:
Crispy 2024-05-04 12:32:11 +02:00
parent 5c282a28ed
commit c345d671dd
2 changed files with 163 additions and 143 deletions

View file

@ -27,8 +27,8 @@ pub struct Rule {
variants: Vec<SubRule>, variants: Vec<SubRule>,
pub enabled: bool, pub enabled: bool,
// probability: u8 // probability: u8
pub flip_h: bool, pub flip_x: bool,
pub flip_v: bool, pub flip_y: bool,
pub rotate: bool, pub rotate: bool,
} }
@ -121,8 +121,8 @@ impl Rule {
enabled: false, enabled: false,
base: SubRule::new(), base: SubRule::new(),
variants: vec![SubRule::new()], variants: vec![SubRule::new()],
flip_h: false, flip_x: false,
flip_v: false, flip_y: false,
rotate: false, rotate: false,
} }
} }
@ -192,7 +192,7 @@ impl Rule {
variants.extend_from_slice(&new); variants.extend_from_slice(&new);
} }
if self.flip_h { if self.flip_x {
transform_variants(&mut self.variants, |b| { transform_variants(&mut self.variants, |b| {
let mut new = b.clone(); let mut new = b.clone();
for y in 0..new.height { for y in 0..new.height {
@ -204,7 +204,7 @@ impl Rule {
new new
}); });
} }
if self.flip_v { if self.flip_y {
transform_variants(&mut self.variants, |b| { transform_variants(&mut self.variants, |b| {
let mut new = b.clone(); let mut new = b.clone();
for y in 0..new.height { for y in 0..new.height {
@ -302,7 +302,7 @@ impl Dish {
(RuleCellFrom::One(Cell(0)), RuleCellTo::One(Cell(1))), (RuleCellFrom::One(Cell(0)), RuleCellTo::One(Cell(1))),
], ],
}, },
flip_h: true, flip_x: true,
..Rule::new() ..Rule::new()
}, },
]; ];

View file

@ -10,7 +10,7 @@ use eframe::{
epaint::Hsva, epaint::Hsva,
NativeOptions, NativeOptions,
}; };
use egui::PointerButton; use egui::{collapsing_header::CollapsingState, PointerButton};
use native_dialog::FileDialog; use native_dialog::FileDialog;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -176,16 +176,16 @@ impl eframe::App for UScope {
let mut to_remove = None; let mut to_remove = None;
let mut to_clone = None; let mut to_clone = None;
for (i, rule) in self.dish.rules.iter_mut().enumerate() { for (i, rule) in self.dish.rules.iter_mut().enumerate() {
ui.separator(); // ui.separator();
rule_editor(ui, rule, &self.cell_types, &self.dish.cell_groups); rule_editor(
ui.horizontal(|ui| { ui,
if ui.button("delete").clicked() { rule,
to_remove = Some(i); i,
} &self.cell_types,
if ui.button("copy").clicked() { &self.dish.cell_groups,
to_clone = Some(i); &mut to_remove,
} &mut to_clone,
}); );
} }
if let Some(i) = to_remove { if let Some(i) = to_remove {
self.dish.rules.remove(i); self.dish.rules.remove(i);
@ -246,138 +246,158 @@ const CSIZE: f32 = 24.;
const RESIZE_BUTTON_WIDTH: f32 = 8.; const RESIZE_BUTTON_WIDTH: f32 = 8.;
const OUTLINE: (f32, Color32) = (2., Color32::GRAY); const OUTLINE: (f32, Color32) = (2., Color32::GRAY);
fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData], groups: &[Vec<Option<Cell>>]) { fn rule_editor(
ui.checkbox(&mut rule.enabled, "enable rule"); ui: &mut Ui,
ui.horizontal(|ui| { rule: &mut Rule,
ui.label("flip"); index: usize,
if ui.checkbox(&mut rule.flip_h, "H").changed() { cells: &[CellData],
rule.generate_variants(); groups: &[Vec<Option<Cell>>],
} to_remove: &mut Option<usize>,
if ui.checkbox(&mut rule.flip_v, "V").changed() { to_clone: &mut Option<usize>,
rule.generate_variants(); ) {
} let id = ui.make_persistent_id(format!("rule {index}"));
}); CollapsingState::load_with_default_open(ui.ctx(), id, true)
if ui.checkbox(&mut rule.rotate, "rotate").changed() { .show_header(ui, |ui| {
rule.generate_variants(); ui.checkbox(&mut rule.enabled, &rule.name);
} if ui.button("delete").clicked() {
*to_remove = Some(index);
let cells_y = rule.height(); }
let cells_x = rule.width(); if ui.button("copy").clicked() {
let patt_width = CSIZE * cells_x as f32; *to_clone = Some(index);
let patt_height = CSIZE * cells_y as f32; }
})
let (_, bounds) = ui.allocate_space(Vec2::new( .body(|ui| {
patt_width * 2. + RESIZE_BUTTON_WIDTH * 4. + CSIZE, ui.text_edit_singleline(&mut rule.name);
patt_height + RESIZE_BUTTON_WIDTH * 2., ui.horizontal(|ui| {
)); if ui.checkbox(&mut rule.flip_x, "flip X").changed() {
rule.generate_variants();
let from_cells_rect = Rect::from_min_size( }
bounds.min + Vec2::splat(RESIZE_BUTTON_WIDTH), if ui.checkbox(&mut rule.flip_y, "flip Y").changed() {
Vec2::new(patt_width, patt_height), rule.generate_variants();
); }
let to_cells_rect = Rect::from_min_size( });
bounds.min if ui.checkbox(&mut rule.rotate, "rotate").changed() {
+ Vec2::splat(RESIZE_BUTTON_WIDTH)
+ Vec2::X * (patt_width + RESIZE_BUTTON_WIDTH * 2. + CSIZE),
Vec2::new(patt_width, patt_height),
);
let mut overlay_lines = Vec::new();
for x in 0..cells_x {
for y in 0..cells_y {
let (left, right) = rule.get_mut(x, y);
let changed_left =
rule_cell_edit_from(ui, from_cells_rect.min, left, x, y, cells, groups);
let changed_right = rule_cell_edit_to(
ui,
to_cells_rect.min,
right,
(x, y),
cells,
groups,
(cells_x, cells_y),
&mut overlay_lines,
);
if changed_left || changed_right {
rule.generate_variants(); rule.generate_variants();
} }
}
}
let delete_mode = ui.input(|i| i.modifiers.shift); let cells_y = rule.height();
let cells_x = rule.width();
let patt_width = CSIZE * cells_x as f32;
let patt_height = CSIZE * cells_y as f32;
let mut resize_box = |x, y, w, h| { let (_, bounds) = ui.allocate_space(Vec2::new(
let rect_a = Rect::from_min_size(Pos2::new(x, y), Vec2::new(w, h)); patt_width * 2. + RESIZE_BUTTON_WIDTH * 4. + CSIZE,
let a = ui.allocate_rect(rect_a, Sense::click()); patt_height + RESIZE_BUTTON_WIDTH * 2.,
let rect_b = rect_a.translate(to_cells_rect.min - from_cells_rect.min); ));
let b = ui.allocate_rect(rect_b, Sense::click());
let result = a.union(b); let from_cells_rect = Rect::from_min_size(
let color = if result.hovered() { bounds.min + Vec2::splat(RESIZE_BUTTON_WIDTH),
if delete_mode { Vec2::new(patt_width, patt_height),
Color32::RED );
} else { let to_cells_rect = Rect::from_min_size(
Color32::GRAY bounds.min
+ Vec2::splat(RESIZE_BUTTON_WIDTH)
+ Vec2::X * (patt_width + RESIZE_BUTTON_WIDTH * 2. + CSIZE),
Vec2::new(patt_width, patt_height),
);
let mut overlay_lines = Vec::new();
for x in 0..cells_x {
for y in 0..cells_y {
let (left, right) = rule.get_mut(x, y);
let changed_left =
rule_cell_edit_from(ui, from_cells_rect.min, left, x, y, cells, groups);
let changed_right = rule_cell_edit_to(
ui,
to_cells_rect.min,
right,
(x, y),
cells,
groups,
(cells_x, cells_y),
&mut overlay_lines,
);
if changed_left || changed_right {
rule.generate_variants();
}
}
} }
} else {
Color32::DARK_GRAY
};
ui.painter_at(bounds).rect_filled(rect_a, 0., color);
ui.painter_at(bounds).rect_filled(rect_b, 0., color);
result.clicked() let delete_mode = ui.input(|i| i.modifiers.shift);
};
if resize_box(
bounds.min.x,
bounds.min.y + RESIZE_BUTTON_WIDTH,
RESIZE_BUTTON_WIDTH,
patt_height,
) {
if delete_mode {
rule.resize(Rule::SHRINK_LEFT);
} else {
rule.resize(Rule::EXTEND_LEFT);
}
}
if resize_box(
from_cells_rect.max.x,
bounds.min.y + RESIZE_BUTTON_WIDTH,
RESIZE_BUTTON_WIDTH,
patt_height,
) {
if delete_mode {
rule.resize(Rule::SHRINK_RIGHT);
} else {
rule.resize(Rule::EXTEND_RIGHT);
}
}
if resize_box(
bounds.min.x + RESIZE_BUTTON_WIDTH,
bounds.min.y,
patt_width,
RESIZE_BUTTON_WIDTH,
) {
if delete_mode {
rule.resize(Rule::SHRINK_UP);
} else {
rule.resize(Rule::EXTEND_UP);
}
}
if resize_box(
bounds.min.x + RESIZE_BUTTON_WIDTH,
bounds.max.y - RESIZE_BUTTON_WIDTH,
patt_width,
RESIZE_BUTTON_WIDTH,
) {
if delete_mode {
rule.resize(Rule::SHRINK_DOWN);
} else {
rule.resize(Rule::EXTEND_DOWN);
}
}
for (a, b) in overlay_lines { let mut resize_box = |x, y, w, h| {
ui.painter().line_segment([a, b], (2., Color32::WHITE)); let rect_a = Rect::from_min_size(Pos2::new(x, y), Vec2::new(w, h));
} let a = ui.allocate_rect(rect_a, Sense::click());
let rect_b = rect_a.translate(to_cells_rect.min - from_cells_rect.min);
let b = ui.allocate_rect(rect_b, Sense::click());
let result = a.union(b);
let color = if result.hovered() {
if delete_mode {
Color32::RED
} else {
Color32::GRAY
}
} else {
Color32::DARK_GRAY
};
ui.painter_at(bounds).rect_filled(rect_a, 0., color);
ui.painter_at(bounds).rect_filled(rect_b, 0., color);
result.clicked()
};
if resize_box(
bounds.min.x,
bounds.min.y + RESIZE_BUTTON_WIDTH,
RESIZE_BUTTON_WIDTH,
patt_height,
) {
if delete_mode {
rule.resize(Rule::SHRINK_LEFT);
} else {
rule.resize(Rule::EXTEND_LEFT);
}
}
if resize_box(
from_cells_rect.max.x,
bounds.min.y + RESIZE_BUTTON_WIDTH,
RESIZE_BUTTON_WIDTH,
patt_height,
) {
if delete_mode {
rule.resize(Rule::SHRINK_RIGHT);
} else {
rule.resize(Rule::EXTEND_RIGHT);
}
}
if resize_box(
bounds.min.x + RESIZE_BUTTON_WIDTH,
bounds.min.y,
patt_width,
RESIZE_BUTTON_WIDTH,
) {
if delete_mode {
rule.resize(Rule::SHRINK_UP);
} else {
rule.resize(Rule::EXTEND_UP);
}
}
if resize_box(
bounds.min.x + RESIZE_BUTTON_WIDTH,
bounds.max.y - RESIZE_BUTTON_WIDTH,
patt_width,
RESIZE_BUTTON_WIDTH,
) {
if delete_mode {
rule.resize(Rule::SHRINK_DOWN);
} else {
rule.resize(Rule::EXTEND_DOWN);
}
}
for (a, b) in overlay_lines {
ui.painter().line_segment([a, b], (2., Color32::WHITE));
}
});
} }
fn rule_cell_edit_from( fn rule_cell_edit_from(