Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c03cdb
added buttons to UI with preliminary icons
podzolelements Aug 3, 2025
d543167
horizontal mirroring is functional for BrushStrokes
podzolelements Aug 3, 2025
039277e
center horizontal mirrors on selection area, not individual strokes
podzolelements Aug 3, 2025
761ede0
horizontal mirroring of ShapeStrokes is fully functional
podzolelements Aug 4, 2025
8555e6d
apply code formatting rules, no functional changes
podzolelements Aug 4, 2025
a5c3d41
horizontal mirroring functional on both bitmapped and vector images
podzolelements Aug 5, 2025
1eaaff6
vertical mirroring fully implemented
podzolelements Aug 6, 2025
a40d12a
update icons
podzolelements Aug 7, 2025
bd036d3
condense mirror_stroke_* functions with orientation parameter
podzolelements Aug 21, 2025
7fe6cc3
move point mirroring functions into rnote-compose
podzolelements Aug 21, 2025
0f64659
move affine matrix transformations into Transform
podzolelements Aug 22, 2025
d95cbb4
add mirroring to Segments and Elements; internalize PenPath mirroring
podzolelements Aug 22, 2025
ee8fd4d
move mirroring logic out of Stroke for point-based shapes
podzolelements Aug 22, 2025
9036e09
add mirroring for transform-based shapes
podzolelements Aug 22, 2025
01d37ce
add direct mirroring to BrushStrokes
podzolelements Aug 22, 2025
fa56d6f
add mirroring as an operation to the Transformable trait
podzolelements Aug 22, 2025
5061f26
condense Transformable mirror functions with orientation paremeter
podzolelements Aug 23, 2025
27bb53a
add orientation based mirror_point
podzolelements Aug 23, 2025
bff1482
fix code style, no functional changes
podzolelements Aug 24, 2025
a1b167f
prevent mirror operations with TextStrokes selected
podzolelements Aug 25, 2025
019285a
add keyboard shortcuts
podzolelements Aug 26, 2025
af9d5d0
change keyboard shortcuts and bind them to gtk accels
podzolelements Aug 26, 2025
9a63a48
modify WidgetFlags to send a popup message to the user
podzolelements Aug 26, 2025
e7322e0
relocate gettext call
podzolelements Aug 27, 2025
4159527
properly update stroke geometry
podzolelements Aug 27, 2025
53da69d
move WidgetFlags text popup proccessing into rnote-ui
podzolelements Aug 28, 2025
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
34 changes: 12 additions & 22 deletions crates/rnote-engine/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,30 +754,20 @@ impl Engine {
| self.update_rendering_current_viewport()
}

pub fn mirror_horizontal_selection(&mut self) -> Option<WidgetFlags> {
self.store
.mirror_stroke(
&self.store.selection_keys_as_rendered(),
MirrorOrientation::Horizontal,
)
.map(|widget_flags| {
widget_flags
| self.record(Instant::now())
| self.update_content_rendering_current_viewport()
})
pub fn mirror_horizontal_selection(&mut self) -> WidgetFlags {
self.store.mirror_stroke(
&self.store.selection_keys_as_rendered(),
MirrorOrientation::Horizontal,
) | self.record(Instant::now())
| self.update_content_rendering_current_viewport()
}

pub fn mirror_vertical_selection(&mut self) -> Option<WidgetFlags> {
self.store
.mirror_stroke(
&self.store.selection_keys_as_rendered(),
MirrorOrientation::Vertical,
)
.map(|widget_flags| {
widget_flags
| self.record(Instant::now())
| self.update_content_rendering_current_viewport()
})
pub fn mirror_vertical_selection(&mut self) -> WidgetFlags {
self.store.mirror_stroke(
&self.store.selection_keys_as_rendered(),
MirrorOrientation::Vertical,
) | self.record(Instant::now())
| self.update_content_rendering_current_viewport()
}

pub fn select_with_bounds(
Expand Down
32 changes: 14 additions & 18 deletions crates/rnote-engine/src/store/stroke_comp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,30 +305,26 @@ impl StrokeStore {
&mut self,
keys: &[StrokeKey],
orientation: MirrorOrientation,
) -> Option<WidgetFlags> {
) -> WidgetFlags {
let mut widget_flags = WidgetFlags::default();

if keys.is_empty() {
return Some(widget_flags);
return widget_flags;
}

let mut stroke_contains_text = false;
keys.iter().for_each(|&key| {
if let Some(stroke) = Arc::make_mut(&mut self.stroke_components)
.get_mut(key)
.map(Arc::make_mut)
{
match stroke {
Stroke::TextStroke(_text_stroke) => {
stroke_contains_text = true;
}
_ => {}
}
}
let stroke_contains_text = keys.iter().any(|&key| {
matches!(
Arc::make_mut(&mut self.stroke_components)
.get_mut(key)
.map(Arc::make_mut),
Some(Stroke::TextStroke(_))
)
});

if stroke_contains_text {
return None;
widget_flags.popup_message =
Some("Mirroring selections containing text is not supported".to_string());
return widget_flags;
}

let all_stroke_bounds = self.strokes_bounds(keys);
Expand Down Expand Up @@ -363,7 +359,7 @@ impl StrokeStore {
if let (Some(min_component), Some(max_component)) = (min_component, max_component) {
(min_component + max_component) / 2.0
} else {
return Some(widget_flags);
return widget_flags;
};

keys.iter().for_each(|&key| {
Expand All @@ -379,7 +375,7 @@ impl StrokeStore {
widget_flags.redraw = true;
widget_flags.store_modified = true;

Some(widget_flags)
widget_flags
}

/// Invert the stroke, text and fill color of the given keys.
Expand Down
8 changes: 8 additions & 0 deletions crates/rnote-engine/src/widgetflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub struct WidgetFlags {
/// Meaning, when enabled instead of key events, text events are then emitted
/// for regular unicode text. Used when writing text with the typewriter.
pub enable_text_preprocessing: Option<bool>,
/// If Some, a popup message is sent to the user, through a dispatch_toast_text message.
/// Intended to notify the user that an operation they performed could not be completed
/// or is not possible
pub popup_message: Option<String>,
}

impl Default for WidgetFlags {
Expand All @@ -42,6 +46,7 @@ impl Default for WidgetFlags {
hide_undo: None,
hide_redo: None,
enable_text_preprocessing: None,
popup_message: None,
}
}
}
Expand Down Expand Up @@ -74,5 +79,8 @@ impl std::ops::BitOrAssign for WidgetFlags {
if rhs.enable_text_preprocessing.is_some() {
self.enable_text_preprocessing = rhs.enable_text_preprocessing;
}
if rhs.popup_message.is_some() {
self.popup_message = rhs.popup_message.clone();
}
}
}
22 changes: 5 additions & 17 deletions crates/rnote-ui/src/appwindow/actions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Imports
use crate::{RnAppWindow, RnCanvas, config, dialogs, overlays};
use crate::{RnAppWindow, RnCanvas, config, dialogs};
use gettextrs::gettext;
use gtk4::gio::InputStream;
use gtk4::graphene;
Expand Down Expand Up @@ -478,14 +478,8 @@ impl RnAppWindow {
let Some(canvas) = appwindow.active_tab_canvas() else {
return;
};
if let Some(widget_flags) = canvas.engine_mut().mirror_horizontal_selection() {
appwindow.handle_widget_flags(widget_flags, &canvas);
} else {
appwindow.overlays().dispatch_toast_text(
&gettext("Mirroring selections containing text is not supported"),
overlays::TEXT_TOAST_TIMEOUT_DEFAULT,
);
}
let widget_flags = canvas.engine_mut().mirror_horizontal_selection();
appwindow.handle_widget_flags(widget_flags, &canvas);
}
));

Expand All @@ -497,14 +491,8 @@ impl RnAppWindow {
let Some(canvas) = appwindow.active_tab_canvas() else {
return;
};
if let Some(widget_flags) = canvas.engine_mut().mirror_vertical_selection() {
appwindow.handle_widget_flags(widget_flags, &canvas);
} else {
appwindow.overlays().dispatch_toast_text(
&gettext("Mirroring selections containing text is not supported"),
overlays::TEXT_TOAST_TIMEOUT_DEFAULT,
);
}
let widget_flags = canvas.engine_mut().mirror_vertical_selection();
appwindow.handle_widget_flags(widget_flags, &canvas);
}
));

Expand Down
8 changes: 7 additions & 1 deletion crates/rnote-ui/src/appwindow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod imp;
// Imports
use crate::{
FileType, RnApp, RnCanvas, RnCanvasWrapper, RnMainHeader, RnOverlays, RnSidebar, config,
dialogs, env,
dialogs, env, overlays,
};
use adw::{prelude::*, subclass::prelude::*};
use core::cell::{Ref, RefMut};
Expand Down Expand Up @@ -335,6 +335,12 @@ impl RnAppWindow {
if let Some(enable_text_preprocessing) = widget_flags.enable_text_preprocessing {
canvas.set_text_preprocessing(enable_text_preprocessing);
}
if let Some(popup_message) = widget_flags.popup_message {
self.overlays().dispatch_toast_text(
&gettext(popup_message),
overlays::TEXT_TOAST_TIMEOUT_DEFAULT,
);
}
}

/// Get the active (selected) tab page.
Expand Down