Add more feature for draw app

This commit is contained in:
daleclack 2024-02-20 10:36:38 +08:00
parent bfde3b616e
commit 25cc05a698
1 changed files with 203 additions and 22 deletions

View File

@ -1,24 +1,43 @@
#include "DrawApp.h"
#include "MyFinder.h"
typedef enum
{
Freehand,
Circle,
Line,
Rectangle
} DrawMode;
typedef enum
{
Begin,
Update,
End
} DrawProc;
struct _DrawApp
{
GtkWindow parent_instance;
// Child widgets
GtkWidget *draw_area;
GtkWidget *color_btn, *fill_btn;
GtkWidget *fill_check;
GtkWidget *main_label, *size_label;
GtkWidget *left_box, *main_box, *btn_box;
GtkWidget *btn_free, *btn_line, *btn_circle, *btn_rectangle;
GtkWidget *btn_fill, *btn_color, *label_color, *label_size;
GtkWidget *size_scale;
GtkWidget *btn_save, *btn_clear, *btn_exit;
GtkColorDialog *fill_dialog, *pen_dialog;
// Drawing Area
cairo_surface_t *surface;
GtkGesture *drag;
double start_x, start_y;
double prev_x, prev_y;
// Draw Mode config
DrawMode draw_mode;
};
G_DEFINE_TYPE(DrawApp, draw_app, GTK_TYPE_WINDOW)
@ -65,40 +84,104 @@ static void draw_app_draw(GtkDrawingArea *da,
cairo_paint(cr);
}
static void draw_brush(DrawApp *app, double x, double y, gboolean first)
static void draw_brush(DrawApp *app, double x, double y, DrawProc process)
{
GdkRectangle update_rect;
cairo_t *cr;
GtkWidget *widget = app->draw_area;
const GdkRGBA *pen_color, *fill_color;
if (app->surface == NULL ||
cairo_image_surface_get_width(app->surface) != gtk_widget_get_width(widget) ||
cairo_image_surface_get_height(app->surface) != gtk_widget_get_height(widget))
create_surface(app);
update_rect.x = x - 3;
update_rect.y = y - 3;
update_rect.width = 6;
update_rect.height = 6;
// Get Color configs
pen_color = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(app->btn_color));
fill_color = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(app->btn_fill));
// Get config for fill color
gboolean fill = gtk_check_button_get_active(GTK_CHECK_BUTTON(app->fill_check));
/* Paint to the surface, where we store our state */
cr = cairo_create(app->surface);
cairo_set_line_width(cr, 6);
gdk_cairo_rectangle(cr, &update_rect);
cairo_fill(cr);
// Get Line width config
double line_width = gtk_range_get_value(GTK_RANGE(app->size_scale));
cairo_set_line_width(cr, line_width);
// Draw lines to link points
if (!first)
// Color for line and freehand drawing
gdk_cairo_set_source_rgba(cr, pen_color);
// Draw with the mode
switch (app->draw_mode)
{
cairo_move_to(cr, app->prev_x, app->prev_y);
cairo_line_to(cr, x, y);
cairo_stroke(cr);
}
case DrawMode::Freehand:
update_rect.x = x - (line_width / 2.0);
update_rect.y = y - (line_width / 2.0);
update_rect.width = line_width;
update_rect.height = line_width;
// Update position for next point
app->prev_x = x;
app->prev_y = y;
gdk_cairo_rectangle(cr, &update_rect);
cairo_fill(cr);
// Draw lines to link points
if (process != DrawProc::Begin)
{
cairo_move_to(cr, app->prev_x, app->prev_y);
cairo_line_to(cr, x, y);
cairo_stroke(cr);
}
// Update position for next point
app->prev_x = x;
app->prev_y = y;
break;
case DrawMode::Circle:
if (process == DrawProc::End)
{
cairo_move_to(cr, app->start_x, app->start_y);
double x1 = fabs(x - (app->start_x));
double y1 = fabs(y - (app->start_y));
double radios = sqrt(x1 * x1 + y1 * y1);
cairo_arc(cr, app->start_x, app->start_y, radios, 0, 2.0 * G_PI);
cairo_stroke(cr);
// If fill check is enable, fill color
if(fill)
{
gdk_cairo_set_source_rgba(cr, fill_color);
cairo_arc(cr, app->start_x, app->start_y, radios, 0, 2.0 * G_PI);
cairo_fill(cr);
}
}
break;
case DrawMode::Line:
if (process == DrawProc::End)
{
cairo_move_to(cr, app->start_x, app->start_y);
cairo_line_to(cr, x, y);
cairo_stroke(cr);
}
break;
case DrawMode::Rectangle:
if (process == DrawProc::End)
{
double width = fabs(x - (app->start_x));
double height = fabs(y - (app->start_y));
cairo_rectangle(cr, app->start_x, app->start_y, width, height);
cairo_stroke(cr);
// If fill check is enable, fill color
if(fill)
{
gdk_cairo_set_source_rgba(cr, fill_color);
cairo_rectangle(cr, app->start_x, app->start_y, width, height);
cairo_fill(cr);
}
}
break;
}
cairo_destroy(cr);
@ -112,7 +195,7 @@ static void drag_begin(GtkGestureDrag *gesture,
{
app->start_x = x;
app->start_y = y;
draw_brush(app, x, y, TRUE);
draw_brush(app, x, y, DrawProc::Begin);
}
static void drag_update(GtkGestureDrag *gesture,
@ -120,7 +203,7 @@ static void drag_update(GtkGestureDrag *gesture,
double y,
DrawApp *app)
{
draw_brush(app, (app->start_x) + x, (app->start_y) + y, FALSE);
draw_brush(app, (app->start_x) + x, (app->start_y) + y, DrawProc::Update);
}
static void drag_end(GtkGestureDrag *gesture,
@ -128,7 +211,63 @@ static void drag_end(GtkGestureDrag *gesture,
double y,
DrawApp *app)
{
draw_brush(app, (app->start_x) + x, (app->start_y) + y, FALSE);
draw_brush(app, (app->start_x) + x, (app->start_y) + y, DrawProc::End);
}
static void btnfree_clicked(GtkButton *btn, DrawApp *self)
{
self->draw_mode = DrawMode::Freehand;
}
static void btncircle_clicked(GtkButton *btn, DrawApp *self)
{
self->draw_mode = DrawMode::Circle;
}
static void btnline_clicked(GtkButton *btn, DrawApp *self)
{
self->draw_mode = DrawMode::Line;
}
static void btnrectangle_clicked(GtkButton *btn, DrawApp *self)
{
self->draw_mode = DrawMode::Rectangle;
}
static void dialog_response(GObject *dialog, GAsyncResult *result, gpointer data)
{
DrawApp *draw_app = DRAW_APP(data);
GFile *file;
file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(dialog), result, NULL);
if (file)
{
char path[PATH_MAX];
char *file_name = g_file_get_path(file);
strncpy(path, file_name, strlen(file_name));
strncat(path, ".png", 4);
cairo_surface_write_to_png(draw_app->surface, path);
g_free(file_name);
}
}
static void btnsave_clicked(GtkButton *btn, DrawApp *self)
{
GtkFileDialog *dialog;
dialog = gtk_file_dialog_new();
gtk_file_dialog_save(dialog, GTK_WINDOW(self), NULL, dialog_response, self);
}
static void btnclear_clicked(GtkButton *btn, DrawApp *self)
{
if (self->surface)
{
// Paint to white to clear
cairo_t *cr = cairo_create(self->surface);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
cairo_destroy(cr);
gtk_widget_queue_draw(self->draw_area);
}
}
static GtkWidget *my_toggle_button_new_from_icon_name(DrawApp *self, const char *icon_name)
@ -164,6 +303,14 @@ static void draw_app_init(DrawApp *self)
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(self->btn_line), GTK_TOGGLE_BUTTON(self->btn_free));
self->btn_rectangle = my_toggle_button_new_from_icon_name(self, "rectangle");
gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(self->btn_rectangle), GTK_TOGGLE_BUTTON(self->btn_free));
g_signal_connect(self->btn_free, "clicked", G_CALLBACK(btnfree_clicked), self);
g_signal_connect(self->btn_circle, "clicked", G_CALLBACK(btncircle_clicked), self);
g_signal_connect(self->btn_line, "clicked", G_CALLBACK(btnline_clicked), self);
g_signal_connect(self->btn_rectangle, "clicked", G_CALLBACK(btnrectangle_clicked), self);
// Default Draw Mode Config
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->btn_free), TRUE);
self->draw_mode = DrawMode::Freehand;
// Create Drawing Area
self->draw_area = gtk_drawing_area_new();
@ -181,6 +328,31 @@ static void draw_app_init(DrawApp *self)
g_signal_connect(self->drag, "drag-update", G_CALLBACK(drag_update), self);
g_signal_connect(self->drag, "drag-end", G_CALLBACK(drag_end), self);
// Create other widgets
self->fill_check = gtk_check_button_new_with_label("Enable Fill Color");
self->fill_dialog = gtk_color_dialog_new();
self->btn_fill = gtk_color_dialog_button_new(self->fill_dialog);
self->label_color = gtk_label_new("Select a color");
self->pen_dialog = gtk_color_dialog_new();
self->btn_color = gtk_color_dialog_button_new(self->pen_dialog);
self->label_size = gtk_label_new("Pen Size");
self->size_scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.1, 20.0, 0.1);
self->btn_save = gtk_button_new_with_label("Save to png");
self->btn_clear = gtk_button_new_with_label("Clear Board");
self->btn_exit = gtk_button_new_with_label("Exit");
// Properties for the buttons
GdkRGBA color = {0, 0, 0, 1};
gtk_color_dialog_button_set_rgba(GTK_COLOR_DIALOG_BUTTON(self->btn_fill), &color);
gtk_color_dialog_button_set_rgba(GTK_COLOR_DIALOG_BUTTON(self->btn_color), &color);
gtk_range_set_value(GTK_RANGE(self->size_scale), 3.0);
gtk_scale_set_draw_value(GTK_SCALE(self->size_scale), TRUE);
gtk_scale_set_value_pos(GTK_SCALE(self->size_scale), GTK_POS_BOTTOM);
gtk_widget_set_valign(self->btn_box, GTK_ALIGN_CENTER);
g_signal_connect(self->btn_save, "clicked", G_CALLBACK(btnsave_clicked), self);
g_signal_connect(self->btn_clear, "clicked", G_CALLBACK(btnclear_clicked), self);
g_signal_connect_swapped(self->btn_exit, "clicked", G_CALLBACK(gtk_window_close), self);
// Add widget to the window
gtk_box_append(GTK_BOX(self->left_box), self->btn_free);
gtk_box_append(GTK_BOX(self->left_box), self->btn_circle);
@ -188,6 +360,15 @@ static void draw_app_init(DrawApp *self)
gtk_box_append(GTK_BOX(self->left_box), self->btn_rectangle);
gtk_box_append(GTK_BOX(self->main_box), self->left_box);
gtk_box_append(GTK_BOX(self->main_box), self->draw_area);
gtk_box_append(GTK_BOX(self->btn_box), self->fill_check);
gtk_box_append(GTK_BOX(self->btn_box), self->btn_fill);
gtk_box_append(GTK_BOX(self->btn_box), self->label_color);
gtk_box_append(GTK_BOX(self->btn_box), self->btn_color);
gtk_box_append(GTK_BOX(self->btn_box), self->label_size);
gtk_box_append(GTK_BOX(self->btn_box), self->size_scale);
gtk_box_append(GTK_BOX(self->btn_box), self->btn_save);
gtk_box_append(GTK_BOX(self->btn_box), self->btn_clear);
gtk_box_append(GTK_BOX(self->btn_box), self->btn_exit);
gtk_box_append(GTK_BOX(self->main_box), self->btn_box);
gtk_window_set_child(GTK_WINDOW(self), self->main_box);
}