diff --git a/include/application.h b/include/application.h index 76e391b..b425917 100644 --- a/include/application.h +++ b/include/application.h @@ -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, diff --git a/include/paint.h b/include/paint.h index 844b213..0f63912 100644 --- a/include/paint.h +++ b/include/paint.h @@ -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); diff --git a/include/swappy.h b/include/swappy.h index aa52e28..26cdc2b 100644 --- a/include/swappy.h +++ b/include/swappy.h @@ -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; diff --git a/res/style/popover.css b/res/style/swappy.css similarity index 100% rename from res/style/popover.css rename to res/style/swappy.css diff --git a/res/swappy.gresource.xml b/res/swappy.gresource.xml index 7ffacae..2a313ed 100644 --- a/res/swappy.gresource.xml +++ b/res/swappy.gresource.xml @@ -1,7 +1,7 @@ - style/popover.css + style/swappy.css swappy.ui \ No newline at end of file diff --git a/res/swappy.ui b/res/swappy.ui index c556d31..20659f7 100644 --- a/res/swappy.ui +++ b/res/swappy.ui @@ -2,12 +2,13 @@ - + + True False edit-redo - + True False edit-undo @@ -18,6 +19,9 @@ False center False + + + @@ -29,12 +33,16 @@ False start - + True - True + False + False + False True - edit-undo1 + edit-undo True + + True @@ -44,12 +52,17 @@ - + True + False False + False True - edit-redo1 + edit-redo True + + + True diff --git a/src/application.c b/src/application.c index 45cab4f..6a37720 100644 --- a/src/application.c +++ b/src/application.c @@ -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); diff --git a/src/clipboard.c b/src/clipboard.c index 8de7d50..1df4014 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -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); diff --git a/src/paint.c b/src/paint.c index c60c969..6cec313 100644 --- a/src/paint.c +++ b/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 diff --git a/src/render.c b/src/render.c index 48cc4ee..213884a 100644 --- a/src/render.c +++ b/src/render.c @@ -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); }