mirror of
https://github.com/jtheoof/swappy.git
synced 2024-08-16 18:20:45 +03:00
feat(ui): add undo/redo
This commit is contained in:
parent
8cd3f134bb
commit
bcc13140eb
@ -10,6 +10,9 @@ void application_finish(struct swappy_state *state);
|
||||
void keypress_handler(GtkWidget *widget, GdkEventKey *event,
|
||||
struct swappy_state *state);
|
||||
|
||||
void undo_clicked_handler(GtkWidget *widget, struct swappy_state *state);
|
||||
void redo_clicked_handler(GtkWidget *widget, struct swappy_state *state);
|
||||
|
||||
gboolean draw_area_handler(GtkWidget *widget, cairo_t *cr,
|
||||
struct swappy_state *state);
|
||||
gboolean draw_area_configure_handler(GtkWidget *widget,
|
||||
|
@ -9,3 +9,4 @@ void paint_commit_temporary(struct swappy_state *state);
|
||||
|
||||
void paint_free(gpointer data);
|
||||
void paint_free_all(struct swappy_state *state);
|
||||
void paint_free_list(GSList **list);
|
||||
|
@ -64,7 +64,15 @@ struct swappy_box {
|
||||
int32_t height;
|
||||
};
|
||||
|
||||
struct swappy_state_ui_painting {
|
||||
struct swappy_state_ui {
|
||||
GtkWindow *window;
|
||||
GtkWidget *area;
|
||||
|
||||
// Undo / Redo
|
||||
GtkButton *undo;
|
||||
GtkButton *redo;
|
||||
|
||||
// Painting Area
|
||||
GtkRadioButton *brush;
|
||||
GtkRadioButton *text;
|
||||
GtkRadioButton *rectangle;
|
||||
@ -76,10 +84,7 @@ struct swappy_state {
|
||||
GResource *resource;
|
||||
GtkApplication *app;
|
||||
|
||||
GtkWindow *window;
|
||||
GtkWidget *area;
|
||||
|
||||
struct swappy_state_ui_painting *popover;
|
||||
struct swappy_state_ui *ui;
|
||||
|
||||
cairo_surface_t *cairo_surface;
|
||||
|
||||
@ -105,6 +110,7 @@ struct swappy_state {
|
||||
struct swappy_box *geometry;
|
||||
|
||||
GSList *paints;
|
||||
GSList *redo_paints;
|
||||
struct swappy_paint *temp_paint;
|
||||
|
||||
int argc;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/swappy">
|
||||
<file>style/popover.css</file>
|
||||
<file>style/swappy.css</file>
|
||||
<file>swappy.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
@ -2,12 +2,13 @@
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkImage" id="edit-redo1">
|
||||
<object class="GtkAccelGroup" id="accel-group"/>
|
||||
<object class="GtkImage" id="edit-redo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-redo</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="edit-undo1">
|
||||
<object class="GtkImage" id="edit-undo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
@ -18,6 +19,9 @@
|
||||
<property name="resizable">False</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="show_menubar">False</property>
|
||||
<accel-groups>
|
||||
<group name="accel-group"/>
|
||||
</accel-groups>
|
||||
<signal name="key-press-event" handler="keypress_handler" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar">
|
||||
@ -29,12 +33,16 @@
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">start</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="undo-button1">
|
||||
<object class="GtkButton" id="undo-button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">edit-undo1</property>
|
||||
<property name="image">edit-undo</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="undo_clicked_handler" swapped="no"/>
|
||||
<accelerator key="z" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@ -44,12 +52,17 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="redo-button1">
|
||||
<object class="GtkButton" id="redo-button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="image">edit-redo1</property>
|
||||
<property name="image">edit-redo</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="redo_clicked_handler" swapped="no"/>
|
||||
<accelerator key="y" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accelerator key="z" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
@ -12,6 +12,33 @@
|
||||
#include "swappy.h"
|
||||
#include "wayland.h"
|
||||
|
||||
static void update_ui(struct swappy_state *state) {
|
||||
GtkWidget *undo = GTK_WIDGET(state->ui->undo);
|
||||
GtkWidget *redo = GTK_WIDGET(state->ui->redo);
|
||||
gboolean undo_sensitive = g_slist_length(state->paints) > 0;
|
||||
gboolean redo_sensitive = g_slist_length(state->redo_paints) > 0;
|
||||
gtk_widget_set_sensitive(undo, undo_sensitive);
|
||||
gtk_widget_set_sensitive(redo, redo_sensitive);
|
||||
}
|
||||
|
||||
static void action_undo(struct swappy_state *state) {
|
||||
GSList *first = state->paints;
|
||||
|
||||
if (first) {
|
||||
state->paints = g_slist_remove_link(state->paints, first);
|
||||
state->redo_paints = g_slist_prepend(state->redo_paints, first->data);
|
||||
}
|
||||
}
|
||||
|
||||
static void action_redo(struct swappy_state *state) {
|
||||
GSList *first = state->redo_paints;
|
||||
|
||||
if (first) {
|
||||
state->redo_paints = g_slist_remove_link(state->redo_paints, first);
|
||||
state->paints = g_slist_prepend(state->paints, first->data);
|
||||
}
|
||||
}
|
||||
|
||||
static void switch_mode_to_brush(struct swappy_state *state) {
|
||||
g_debug("switching mode to brush");
|
||||
state->mode = SWAPPY_PAINT_MODE_BRUSH;
|
||||
@ -65,16 +92,16 @@ void application_finish(struct swappy_state *state) {
|
||||
g_free(state->geometry_str);
|
||||
g_free(state->geometry);
|
||||
g_resources_unregister(state->resource);
|
||||
g_free(state->popover);
|
||||
g_free(state->ui);
|
||||
g_object_unref(state->app);
|
||||
}
|
||||
|
||||
static void action_save_area_to_file(struct swappy_state *state) {
|
||||
g_debug("saving area to file");
|
||||
|
||||
guint width = gtk_widget_get_allocated_width(state->area);
|
||||
guint height = gtk_widget_get_allocated_height(state->area);
|
||||
// GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(state->area));
|
||||
guint width = gtk_widget_get_allocated_width(state->ui->area);
|
||||
guint height = gtk_widget_get_allocated_height(state->ui->area);
|
||||
// GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(state->ui->area));
|
||||
g_debug("generating pixbuf from area");
|
||||
GdkPixbuf *pixbuf =
|
||||
gdk_pixbuf_get_from_surface(state->cairo_surface, 0, 0, width, height);
|
||||
@ -110,6 +137,7 @@ void save_clicked_handler(GtkWidget *widget, struct swappy_state *state) {
|
||||
void clear_clicked_handler(GtkWidget *widget, struct swappy_state *state) {
|
||||
paint_free_all(state);
|
||||
render_state(state);
|
||||
update_ui(state);
|
||||
}
|
||||
|
||||
void copy_clicked_handler(GtkWidget *widget, struct swappy_state *state) {
|
||||
@ -130,27 +158,27 @@ void keypress_handler(GtkWidget *widget, GdkEventKey *event,
|
||||
break;
|
||||
case GDK_KEY_b:
|
||||
switch_mode_to_brush(state);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->popover->brush),
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->brush),
|
||||
true);
|
||||
break;
|
||||
case GDK_KEY_t:
|
||||
switch_mode_to_text(state);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->popover->text),
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->text),
|
||||
true);
|
||||
break;
|
||||
case GDK_KEY_r:
|
||||
switch_mode_to_rectangle(state);
|
||||
gtk_toggle_button_set_active(
|
||||
GTK_TOGGLE_BUTTON(state->popover->rectangle), true);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->rectangle),
|
||||
true);
|
||||
break;
|
||||
case GDK_KEY_o:
|
||||
switch_mode_to_ellipse(state);
|
||||
gtk_toggle_button_set_active(
|
||||
GTK_TOGGLE_BUTTON(state->popover->ellipse), true);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->ellipse),
|
||||
true);
|
||||
break;
|
||||
case GDK_KEY_a:
|
||||
switch_mode_to_arrow(state);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->popover->arrow),
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state->ui->arrow),
|
||||
true);
|
||||
break;
|
||||
default:
|
||||
@ -174,6 +202,18 @@ void keypress_handler(GtkWidget *widget, GdkEventKey *event,
|
||||
}
|
||||
}
|
||||
|
||||
void undo_clicked_handler(GtkWidget *widget, struct swappy_state *state) {
|
||||
action_undo(state);
|
||||
render_state(state);
|
||||
update_ui(state);
|
||||
}
|
||||
|
||||
void redo_clicked_handler(GtkWidget *widget, struct swappy_state *state) {
|
||||
action_redo(state);
|
||||
render_state(state);
|
||||
update_ui(state);
|
||||
}
|
||||
|
||||
gboolean draw_area_handler(GtkWidget *widget, cairo_t *cr,
|
||||
struct swappy_state *state) {
|
||||
cairo_set_source_surface(cr, state->cairo_surface, 0, 0);
|
||||
@ -251,7 +291,9 @@ void draw_area_button_release_handler(GtkWidget *widget, GdkEventButton *event,
|
||||
case SWAPPY_PAINT_MODE_ELLIPSE:
|
||||
case SWAPPY_PAINT_MODE_ARROW:
|
||||
paint_commit_temporary(state);
|
||||
paint_free_list(&state->redo_paints);
|
||||
render_state(state);
|
||||
update_ui(state);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@ -269,8 +311,8 @@ static void apply_css(GtkWidget *widget, GtkStyleProvider *provider) {
|
||||
|
||||
static void load_css(struct swappy_state *state) {
|
||||
GtkCssProvider *provider = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_resource(provider, "/swappy/style/popover.css");
|
||||
apply_css(GTK_WIDGET(state->window), GTK_STYLE_PROVIDER(provider));
|
||||
gtk_css_provider_load_from_resource(provider, "/swappy/style/swappy.css");
|
||||
apply_css(GTK_WIDGET(state->ui->window), GTK_STYLE_PROVIDER(provider));
|
||||
g_object_unref(provider);
|
||||
}
|
||||
|
||||
@ -292,6 +334,9 @@ static bool load_layout(struct swappy_state *state) {
|
||||
GtkWindow *window =
|
||||
GTK_WINDOW(gtk_builder_get_object(builder, "paint-window"));
|
||||
|
||||
state->ui->undo = GTK_BUTTON(gtk_builder_get_object(builder, "undo-button"));
|
||||
state->ui->redo = GTK_BUTTON(gtk_builder_get_object(builder, "redo-button"));
|
||||
|
||||
GtkWidget *area = GTK_WIDGET(gtk_builder_get_object(builder, "paint_area"));
|
||||
|
||||
GtkRadioButton *brush =
|
||||
@ -305,16 +350,16 @@ static bool load_layout(struct swappy_state *state) {
|
||||
GtkRadioButton *arrow =
|
||||
GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "arrow"));
|
||||
|
||||
// gtk_popover_set_relative_to(popover, area);
|
||||
// gtk_popover_set_relative_to(ui, area);
|
||||
gtk_widget_set_size_request(area, geometry->width, geometry->height);
|
||||
|
||||
state->popover->brush = brush;
|
||||
state->popover->text = text;
|
||||
state->popover->rectangle = rectangle;
|
||||
state->popover->ellipse = ellipse;
|
||||
state->popover->arrow = arrow;
|
||||
state->area = area;
|
||||
state->window = window;
|
||||
state->ui->brush = brush;
|
||||
state->ui->text = text;
|
||||
state->ui->rectangle = rectangle;
|
||||
state->ui->ellipse = ellipse;
|
||||
state->ui->arrow = arrow;
|
||||
state->ui->area = area;
|
||||
state->ui->window = window;
|
||||
|
||||
g_object_unref(G_OBJECT(builder));
|
||||
|
||||
@ -389,7 +434,7 @@ bool application_init(struct swappy_state *state) {
|
||||
g_error_free(error);
|
||||
}
|
||||
|
||||
state->popover = g_new(struct swappy_state_ui_painting, 1);
|
||||
state->ui = g_new(struct swappy_state_ui, 1);
|
||||
|
||||
g_resources_register(state->resource);
|
||||
|
||||
|
@ -5,8 +5,8 @@
|
||||
bool clipboard_copy_drawing_area_to_selection(struct swappy_state *state) {
|
||||
g_debug("generating pixbuf from area");
|
||||
GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
guint width = gtk_widget_get_allocated_width(state->area);
|
||||
guint height = gtk_widget_get_allocated_height(state->area);
|
||||
guint width = gtk_widget_get_allocated_width(state->ui->area);
|
||||
guint height = gtk_widget_get_allocated_height(state->ui->area);
|
||||
GdkPixbuf *pixbuf =
|
||||
gdk_pixbuf_get_from_surface(state->cairo_surface, 0, 0, width, height);
|
||||
|
||||
|
18
src/paint.c
18
src/paint.c
@ -16,12 +16,16 @@ void paint_free(gpointer data) {
|
||||
}
|
||||
}
|
||||
|
||||
void paint_free_all(struct swappy_state *state) {
|
||||
if (state->paints) {
|
||||
g_slist_free_full(state->paints, paint_free);
|
||||
state->paints = NULL;
|
||||
void paint_free_list(GSList **list) {
|
||||
if (*list) {
|
||||
g_slist_free_full(*list, paint_free);
|
||||
*list = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void paint_free_all(struct swappy_state *state) {
|
||||
paint_free_list(&state->paints);
|
||||
paint_free_list(&state->redo_paints);
|
||||
paint_free(state->temp_paint);
|
||||
state->temp_paint = NULL;
|
||||
}
|
||||
@ -47,7 +51,7 @@ void paint_add_temporary(struct swappy_state *state, double x, double y,
|
||||
brush->x = x;
|
||||
brush->y = y;
|
||||
|
||||
paint->content.brush.points = g_slist_append(NULL, brush);
|
||||
paint->content.brush.points = g_slist_prepend(NULL, brush);
|
||||
break;
|
||||
case SWAPPY_PAINT_MODE_RECTANGLE:
|
||||
case SWAPPY_PAINT_MODE_ELLIPSE:
|
||||
@ -91,7 +95,7 @@ void paint_update_temporary(struct swappy_state *state, double x, double y) {
|
||||
brush->x = x;
|
||||
brush->y = y;
|
||||
|
||||
paint->content.brush.points = g_slist_append(points, brush);
|
||||
paint->content.brush.points = g_slist_prepend(points, brush);
|
||||
break;
|
||||
case SWAPPY_PAINT_MODE_RECTANGLE:
|
||||
case SWAPPY_PAINT_MODE_ELLIPSE:
|
||||
@ -116,7 +120,7 @@ void paint_commit_temporary(struct swappy_state *state) {
|
||||
if (!paint->can_draw) {
|
||||
paint_free(paint);
|
||||
} else {
|
||||
state->paints = g_slist_append(state->paints, paint);
|
||||
state->paints = g_slist_prepend(state->paints, paint);
|
||||
}
|
||||
|
||||
// Set the temporary paint to NULL but keep the content in memory
|
||||
|
@ -246,7 +246,7 @@ void render_state(struct swappy_state *state) {
|
||||
render_paints(cr, state);
|
||||
|
||||
// Drawing is finished, notify the GtkDrawingArea it needs to be redrawn.
|
||||
gtk_widget_queue_draw(state->area);
|
||||
gtk_widget_queue_draw(state->ui->area);
|
||||
|
||||
cairo_destroy(cr);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user