feat(ui): add undo/redo

This commit is contained in:
Jeremy Attali 2019-12-25 22:32:52 -05:00
parent 8cd3f134bb
commit bcc13140eb
10 changed files with 117 additions and 45 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);
}