Skip to content

Commit 7884b20

Browse files
committed
Add more custom UI
1 parent 2b35093 commit 7884b20

File tree

7 files changed

+161
-40
lines changed

7 files changed

+161
-40
lines changed

libs/silica/src/file.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ pub struct ProcreateFile {
2929
pub composite: Option<SilicaLayer>,
3030
pub layers: SilicaGroup,
3131
pub size: Size<u32>,
32-
pub layer_count: u32,
3332
}
3433

3534
pub struct ProcreateFileMetadata {
@@ -67,4 +66,8 @@ impl ProcreateFile {
6766
) -> Result<(Self, ProcreateFileMetadata), SilicaError> {
6867
ProcreateUnloadedFile::from_ns(&archive, &nka)?.load(dispatch)
6968
}
69+
70+
pub fn layer_count(&self) -> u32 {
71+
self.layers.layer_count()
72+
}
7073
}

libs/silica/src/ir/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ pub struct ProcreateUnloadedFile<'a> {
5757
// videoDuration: String? = "Calculating..."
5858
pub tile_size: u32,
5959

60-
pub layer_count: u32,
61-
6260
info: IRData<'a>,
6361

6462
layers: Vec<SilicaIRHierarchy<'a>>,
@@ -98,8 +96,6 @@ impl<'a> ProcreateUnloadedFile<'a> {
9896
atlas: AtlasTextureTiling::compute_atlas_size(chunk_count, tile_size),
9997
};
10098

101-
let layer_count = layers.iter().map(|ir| ir.count_layer()).sum::<u32>() + 1;
102-
10399
Ok(Self {
104100
info: IRData {
105101
archive,
@@ -132,7 +128,6 @@ impl<'a> ProcreateUnloadedFile<'a> {
132128
vertically: nka.fetch::<bool>(root, "flippedVertically")?,
133129
},
134130
tile_size,
135-
layer_count,
136131
composite: nka.fetch::<SilicaIRLayer>(root, "composite")?,
137132
layers,
138133
})
@@ -177,7 +172,6 @@ impl<'a> ProcreateUnloadedFile<'a> {
177172
flipped: self.flipped,
178173
tile_size: self.tile_size,
179174
size: self.info.size,
180-
layer_count: self.layer_count,
181175
},
182176
ProcreateFileMetadata {
183177
atlas_texture,

libs/silica/src/layers.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ pub enum SilicaHierarchy {
9292
Group(SilicaGroup),
9393
}
9494

95+
impl SilicaHierarchy {
96+
pub fn layer_count(&self) -> u32 {
97+
match self {
98+
SilicaHierarchy::Layer(_) => 1,
99+
SilicaHierarchy::Group(silica_group) => silica_group.layer_count(),
100+
}
101+
}
102+
}
103+
95104
#[derive(Debug, Clone, PartialEq)]
96105
pub struct SilicaGroup {
97106
pub hidden: bool,
@@ -102,6 +111,12 @@ pub struct SilicaGroup {
102111
pub id: u32,
103112
}
104113

114+
impl SilicaGroup {
115+
pub fn layer_count(&self) -> u32 {
116+
self.children.iter().map(|hier| hier.layer_count()).sum()
117+
}
118+
}
119+
105120
#[derive(Debug, Clone, PartialEq)]
106121
pub struct SilicaChunk {
107122
pub col: u32,

src/gui/custom/blend_radio.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use egui::*;
2+
use silicate_compositor::blend::BlendingMode;
3+
4+
pub struct BlendModeRadio<'a> {
5+
value: &'a mut BlendingMode,
6+
}
7+
8+
#[derive(Clone, Copy, Debug)]
9+
pub struct BlendModeRadioLoaded;
10+
11+
impl BlendModeRadioLoaded {
12+
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
13+
ctx.data_mut(|d| d.get_persisted(id))
14+
}
15+
16+
pub fn store(self, ctx: &Context, id: Id) {
17+
ctx.data_mut(|d| d.insert_persisted(id, self));
18+
}
19+
}
20+
21+
impl<'a> BlendModeRadio<'a> {
22+
pub fn new(value: &'a mut BlendingMode) -> Self {
23+
Self { value }
24+
}
25+
26+
fn layout_scroll_area(&mut self, ui: &mut Ui) {
27+
let min_y = ui.min_rect().min.y;
28+
let mut scroll_to_value = 0.0;
29+
let mut scroll = ScrollArea::vertical().max_height(70.0).show(ui, |ui| {
30+
ui.set_width(ui.available_width());
31+
32+
for b in BlendingMode::all() {
33+
let mut frame = egui::Frame::NONE
34+
.inner_margin(Margin::symmetric(10, 3))
35+
.begin(ui);
36+
{
37+
let ui = &mut frame.content_ui;
38+
ui.set_width(ui.available_width());
39+
Label::new(RichText::new(b.as_str()).color(Color32::WHITE))
40+
.selectable(false)
41+
.ui(ui);
42+
}
43+
let response = ui.allocate_rect(frame.content_ui.min_rect(), Sense::click());
44+
45+
if b == self.value {
46+
scroll_to_value = response.rect.min.y - min_y;
47+
frame.frame.fill = super::ACCENT_COLOR;
48+
} else if response.hovered() {
49+
frame.frame.fill = Color32::from_rgb(50, 50, 50)
50+
}
51+
52+
if response.clicked() {
53+
*self.value = *b;
54+
}
55+
frame.end(ui);
56+
}
57+
});
58+
59+
let loaded = BlendModeRadioLoaded::load(ui.ctx(), ui.id()).is_some();
60+
if !loaded {
61+
scroll.state.offset = vec2(0.0, scroll_to_value);
62+
scroll.state.store(ui.ctx(), scroll.id);
63+
}
64+
BlendModeRadioLoaded.store(ui.ctx(), ui.id());
65+
}
66+
67+
pub fn ui(mut self, ui: &mut Ui) -> Response {
68+
let old_value = *self.value;
69+
70+
let mut response = egui::Frame::default()
71+
.inner_margin(Margin::symmetric(0, 5))
72+
.corner_radius(4)
73+
.fill(Color32::from_rgb(20, 20, 20))
74+
.show(ui, |ui| self.layout_scroll_area(ui)).response;
75+
76+
if old_value != *self.value {
77+
response.mark_changed();
78+
}
79+
response
80+
}
81+
}

src/gui/custom/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
pub mod slider;
1+
use egui::Color32;
2+
3+
pub mod opacity_slider;
4+
pub mod blend_radio;
5+
6+
const ACCENT_COLOR: Color32 = Color32::from_rgb(48, 116, 243);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use egui::*;
22

3-
const FILL_COLOR: Color32 = Color32::from_rgb(48, 116, 243);
3+
const FILL_COLOR: Color32 = super::ACCENT_COLOR;
44
const HANDLE_RADIUS: f32 = 5.0;
55

66
#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]

src/gui/layout.rs

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ use egui::load::SizedTexture;
22
use egui::*;
33
use egui_dock::{NodeIndex, SurfaceIndex};
44
use silica::layers::{SilicaGroup, SilicaHierarchy, SilicaLayer};
5-
use silicate_compositor::blend::BlendingMode;
65
use std::collections::HashMap;
76
use std::sync::Arc;
87
use tokio::sync::mpsc::Receiver;
98

109
use crate::app::{App, Instance, InstanceKey, UserEvent};
1110

12-
use super::{canvas::CanvasView, custom::slider::OpacitySlider};
11+
use super::{
12+
canvas::CanvasView,
13+
custom::{blend_radio::BlendModeRadio, opacity_slider::OpacitySlider},
14+
};
1315

1416
struct ControlsGui<'a> {
1517
app: &'a Arc<App>,
@@ -38,7 +40,7 @@ impl ControlsGui<'_> {
3840
ui.label(file.stroke_count.to_string());
3941
ui.end_row();
4042
ui.label("Layer Count");
41-
ui.label(file.layer_count.to_string());
43+
ui.label(file.layer_count().to_string());
4244
ui.end_row();
4345
ui.label("Canvas Size");
4446

@@ -80,9 +82,14 @@ impl ControlsGui<'_> {
8082
.write()
8183
.get_mut(&self.active_canvas)
8284
{
83-
let mut degree = instance.rotation.to_degrees();
84-
ui.add(Slider::new(&mut degree, 0.0..=360.0).suffix(" deg"));
85-
instance.rotation = degree.to_radians();
85+
ui.add(
86+
Slider::new(&mut instance.rotation, 0.0..=std::f32::consts::TAU)
87+
.custom_formatter(|v, _| {
88+
let degree = v.to_degrees();
89+
format!("{degree:.0}")
90+
})
91+
.suffix(" deg"),
92+
);
8693
} else {
8794
ui.label("No file loaded...");
8895
}
@@ -154,20 +161,14 @@ impl ControlsGui<'_> {
154161

155162
fn layout_layer_control(ui: &mut Ui, l: &mut SilicaLayer, changed: &mut bool) {
156163
*changed |= OpacitySlider::new(&mut l.opacity).ui(ui).changed();
157-
158-
ui.style_mut().spacing.combo_width = ui.available_width();
159-
ComboBox::from_id_salt(0)
160-
.selected_text(l.blend.as_str())
161-
.show_ui(ui, |ui| {
162-
for b in BlendingMode::all() {
163-
*changed |= ui.selectable_value(&mut l.blend, *b, b.as_str()).changed();
164-
}
165-
});
164+
ui.add_space(10.0);
165+
*changed |= BlendModeRadio::new(&mut l.blend).ui(ui).changed();
166166

167167
Grid::new(l.id).show(ui, |ui| {
168168
ui.label("Clipped");
169169
*changed |= Checkbox::without_text(&mut l.clipped).ui(ui).changed();
170170
});
171+
ui.add_space(10.0);
171172
}
172173

173174
fn layout_layers_sub(ui: &mut Ui, layers: &mut SilicaGroup, changed: &mut bool) {
@@ -200,13 +201,20 @@ impl ControlsGui<'_> {
200201
);
201202

202203
let header_res = ui.horizontal(|ui| {
203-
let mut frame = egui::Frame::default()
204-
.corner_radius(2.5)
205-
.inner_margin(5.0)
204+
let mut frame = egui::Frame::new()
205+
.corner_radius(3)
206+
.inner_margin(5)
206207
.begin(ui);
207208
{
208209
let ui = &mut frame.content_ui;
209-
if ui.strong(layer_name).clicked() {
210+
if ui
211+
.add(
212+
Label::new(layer_name)
213+
.selectable(false)
214+
.sense(Sense::click()),
215+
)
216+
.clicked()
217+
{
210218
state.toggle(ui);
211219
}
212220
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
@@ -224,17 +232,18 @@ impl ControlsGui<'_> {
224232
}
225233
frame.end(ui);
226234
});
227-
228-
state.show_body_indented(&header_res.response, ui, |ui| {
229-
match layer {
230-
SilicaHierarchy::Layer(layer) => {
235+
match layer {
236+
SilicaHierarchy::Layer(layer) => {
237+
state.show_body_unindented(ui, |ui| {
231238
Self::layout_layer_control(ui, layer, changed);
232-
}
233-
SilicaHierarchy::Group(layer) => {
239+
});
240+
}
241+
SilicaHierarchy::Group(layer) => {
242+
state.show_body_indented(&header_res.response, ui, |ui| {
234243
Self::layout_layers_sub(ui, layer, changed);
235-
}
236-
};
237-
});
244+
});
245+
}
246+
};
238247
});
239248
}
240249

@@ -260,12 +269,25 @@ impl ControlsGui<'_> {
260269
ui.end_row();
261270
ui.label("Background Color");
262271

263-
// Safety: This is trivially safe. The underlying container is of 4 elements.
264-
// This does the same thing as split_array_mut except that is not stabilized yet.
265-
let bg = unsafe { &mut *(file.background_color.as_mut_ptr() as *mut [f32; 3]) };
272+
// Safety: this is trivially safe, N=3 < 4
273+
let bg = unsafe {
274+
file.background_color
275+
.first_chunk_mut::<3>()
276+
.unwrap_unchecked()
277+
};
266278
changed |= ui.color_edit_button_rgb(bg).changed();
267279
});
268280

281+
// let bg = &mut file.background_color;
282+
// let rgb = Rgba::from_rgb(bg[0], bg[1], bg[2]);
283+
// let mut color = Color32::from(rgb);
284+
// let old_value = color;
285+
// color_picker::color_picker_color32(ui, &mut color, color_picker::Alpha::Opaque);
286+
// if old_value != color {
287+
// *bg = Rgba::from(color).to_rgba_unmultiplied();
288+
// changed = true;
289+
// }
290+
269291
instance.tick_change(changed);
270292
} else {
271293
ui.label("No file hierachy.");
@@ -382,6 +404,7 @@ impl ViewerGui {
382404
.style(egui_dock::Style::from_egui(ui.style()))
383405
.show_add_buttons(true)
384406
.show_leaf_close_all_buttons(false)
407+
.show_leaf_collapse_buttons(false)
385408
.show_inside(
386409
ui,
387410
&mut CanvasGui {

0 commit comments

Comments
 (0)