From 64dd251dd018883b1d396372c5ad246b7465db7c Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Wed, 11 Feb 2026 13:44:50 -0700 Subject: [PATCH] init commit --- .../clangd/index/main.c.8F071D70C859F269.idx | Bin 0 -> 14838 bytes .gitignore | 1 + README.md | 28 ++ meson.build | 12 + src/main.c | 344 ++++++++++++++++++ src/meson.build | 13 + src/notes.ui | 99 +++++ src/resources.xml | 7 + src/style.css | 5 + 9 files changed, 509 insertions(+) create mode 100644 .cache/clangd/index/main.c.8F071D70C859F269.idx create mode 100644 .gitignore create mode 100644 README.md create mode 100644 meson.build create mode 100644 src/main.c create mode 100644 src/meson.build create mode 100644 src/notes.ui create mode 100644 src/resources.xml create mode 100644 src/style.css diff --git a/.cache/clangd/index/main.c.8F071D70C859F269.idx b/.cache/clangd/index/main.c.8F071D70C859F269.idx new file mode 100644 index 0000000000000000000000000000000000000000..5b1897456035b524cfac7eb25add12b2a7e514f3 GIT binary patch literal 14838 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

Zf=Cjq%_plS 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 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +typedef enum { TOOL_PEN, TOOL_HIGHLIGHTER, TOOL_ERASER } ToolType; + +typedef struct { + double x, y, pressure; +} Point; + +typedef struct { + GQueue *points; + GdkRGBA color; + double width; + ToolType tool_type; +} Stroke; + +GList *strokes = NULL; +Stroke *current_stroke = NULL; +cairo_surface_t *surface = NULL; + +GtkWidget *canvas = NULL; +GtkWidget *main_window = NULL; + +ToolType current_tool = TOOL_PEN; +int bg_template = 0; + +static void clear_strokes() { + if (!strokes) + return; + + for (GList *l = strokes; l != NULL; l = l->next) { + Stroke *s = (Stroke *)l->data; + if (s->points) { + // Free every Point struct inside the queue + while (!g_queue_is_empty(s->points)) { + g_free(g_queue_pop_head(s->points)); + } + g_queue_free(s->points); + } + g_free(s); + } + g_list_free(strokes); + strokes = NULL; +} + +static void set_cairo_for_stroke(cairo_t *cr, Stroke *s) { + gdk_cairo_set_source_rgba(cr, &s->color); + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); + + if (s->tool_type == TOOL_HIGHLIGHTER) { + cairo_set_operator(cr, CAIRO_OPERATOR_MULTIPLY); + } else if (s->tool_type == TOOL_ERASER) { + cairo_set_operator(cr, CAIRO_OPERATOR_DEST_OUT); + } else { + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + } +} + +static void draw_stroke_path(cairo_t *cr, Stroke *s) { + if (!s || !s->points) + return; + + if (g_queue_get_length(s->points) == 1) { + set_cairo_for_stroke(cr, s); + Point *p = (Point *)s->points->head->data; + cairo_arc(cr, p->x, p->y, s->width / 2.0, 0, 2 * G_PI); + cairo_fill(cr); + return; + } + + set_cairo_for_stroke(cr, s); + + GList *l = s->points->head; + Point *p1 = (Point *)l->data; + cairo_move_to(cr, p1->x, p1->y); + + for (l = l->next; l != NULL; l = l->next) { + Point *p2 = (Point *)l->data; + double pressure = (p1->pressure + p2->pressure) / 2.0; + double line_width = s->width; + + if (s->tool_type == TOOL_PEN) { + line_width *= MAX(0.2, pressure * 1.5); + } + + cairo_set_line_width(cr, line_width); + cairo_line_to(cr, p2->x, p2->y); + cairo_stroke(cr); + + cairo_move_to(cr, p2->x, p2->y); + p1 = p2; + } +} + +static void draw_background(cairo_t *cr, int w, int h) { + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_paint(cr); +} + +static void ensure_surface(GtkWidget *widget) { + int width = gtk_widget_get_width(widget); + int height = gtk_widget_get_height(widget); + + // 1. Prevent 0-sized surface creation (The Portrait fix) + if (width <= 1 || height <= 1) + return; + + if (surface && (cairo_image_surface_get_width(surface) != width || + cairo_image_surface_get_height(surface) != height)) { + + // 2. Optional: Copy old content to new surface so you don't lose notes on + // rotation + cairo_surface_t *old_surface = surface; + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cr = cairo_create(surface); + + draw_background(cr, width, height); + + // Re-draw all strokes onto the new size + for (GList *l = strokes; l != NULL; l = l->next) { + draw_stroke_path(cr, (Stroke *)l->data); + } + + cairo_destroy(cr); + cairo_surface_destroy(old_surface); + } + + if (!surface) { + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + } +} + +void on_draw(GtkDrawingArea *area, cairo_t *cr, int width, int height, + gpointer data) { + draw_background(cr, width, height); + ensure_surface(GTK_WIDGET(area)); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + if (current_stroke) { + draw_stroke_path(cr, current_stroke); + } +} + +void start_new_stroke(double x, double y, GdkEvent *event) { + current_stroke = g_new0(Stroke, 1); + current_stroke->points = g_queue_new(); + current_stroke->tool_type = current_tool; + + GdkDevice *device = gdk_event_get_device(event); + GdkDeviceTool *tool = gdk_device_get_device_tool(device); + + if (tool) { + GdkDeviceToolType type = gdk_device_tool_get_tool_type(tool); + g_print("DEBUG: Tool Type: %d\n", type); + if (type == GDK_DEVICE_TOOL_TYPE_ERASER) { + current_stroke->tool_type = TOOL_ERASER; + } + } + + GdkModifierType state = gdk_event_get_modifier_state(event); + if (state & GDK_BUTTON3_MASK) { + current_stroke->tool_type = TOOL_HIGHLIGHTER; + g_print("DEBUG: Button 3 Mask Detected\n"); + } + + if (current_stroke->tool_type == TOOL_PEN) { + gdk_rgba_parse(¤t_stroke->color, "black"); + current_stroke->width = 2.0; + } else if (current_stroke->tool_type == TOOL_HIGHLIGHTER) { + gdk_rgba_parse(¤t_stroke->color, "#FFF500"); + current_stroke->color.alpha = 0.4; + current_stroke->width = 20.0; + } else if (current_stroke->tool_type == TOOL_ERASER) { + gdk_rgba_parse(¤t_stroke->color, "#FFFFFF"); + current_stroke->color.alpha = 1.0; + current_stroke->width = 30.0; + } + + Point *p = g_new0(Point, 1); + p->x = x; + p->y = y; + p->pressure = 0.5; + gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &p->pressure); + + g_queue_push_tail(current_stroke->points, p); + gtk_widget_queue_draw(canvas); +} + +static void on_motion(GtkEventControllerMotion *controller, double x, double y, + gpointer user_data) { + GdkEvent *event = + gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); + GdkModifierType state = gdk_event_get_modifier_state(event); + + gboolean is_drawing = + (state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)); + + if (is_drawing) { + if (!current_stroke) { + start_new_stroke(x, y, event); + } else { + Point *p = g_new0(Point, 1); + p->x = x; + p->y = y; + p->pressure = 0.5; + gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &p->pressure); + g_queue_push_tail(current_stroke->points, p); + gtk_widget_queue_draw(canvas); + } + } else { + if (current_stroke) { + cairo_t *cr = cairo_create(surface); + draw_stroke_path(cr, current_stroke); + cairo_destroy(cr); + strokes = g_list_append(strokes, current_stroke); + current_stroke = NULL; + gtk_widget_queue_draw(canvas); + } + } +} + +static void on_pressed(GtkGestureClick *gesture, int n_press, double x, + double y, gpointer user_data) { + if (current_stroke) + return; + + GdkEvent *event = + gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(gesture)); + start_new_stroke(x, y, event); +} + +static void on_released(GtkGestureClick *gesture, int n_press, double x, + double y, gpointer user_data) { + if (current_stroke) { + cairo_t *cr = cairo_create(surface); + draw_stroke_path(cr, current_stroke); + cairo_destroy(cr); + strokes = g_list_append(strokes, current_stroke); + current_stroke = NULL; + } + gtk_widget_queue_draw(canvas); +} + +static void on_tool_changed(GtkToggleButton *btn, gpointer user_data) { + if (!gtk_toggle_button_get_active(btn)) + return; + const char *id = gtk_buildable_get_buildable_id(GTK_BUILDABLE(btn)); + if (g_strcmp0(id, "btn_pen") == 0) + current_tool = TOOL_PEN; + else if (g_strcmp0(id, "btn_highlighter") == 0) + current_tool = TOOL_HIGHLIGHTER; + else if (g_strcmp0(id, "btn_eraser") == 0) + current_tool = TOOL_ERASER; +} + +static void on_template_changed(GtkComboBoxText *combo, gpointer user_data) { + bg_template = gtk_combo_box_get_active(GTK_COMBO_BOX(combo)); + if (surface) { + cairo_surface_destroy(surface); + surface = NULL; + } + gtk_widget_queue_draw(canvas); +} + +static void on_export_response(GtkNativeDialog *d, int r, gpointer u) { + if (r == GTK_RESPONSE_ACCEPT) { + GFile *f = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(d)); + char *p = g_file_get_path(f); + if (surface) + cairo_surface_write_to_png(surface, p); + g_free(p); + g_object_unref(f); + } + g_object_unref(d); +} + +static void action_export(GSimpleAction *a, GVariant *p, gpointer u) { + if (!surface) + return; + GtkFileChooserNative *d = gtk_file_chooser_native_new( + "Export", GTK_WINDOW(main_window), GTK_FILE_CHOOSER_ACTION_SAVE, "Save", + "Cancel"); + g_signal_connect(d, "response", G_CALLBACK(on_export_response), NULL); + gtk_native_dialog_show(GTK_NATIVE_DIALOG(d)); +} + +static void on_save_response(GtkNativeDialog *d, int r, gpointer data) {} + +static void action_save(GSimpleAction *a, GVariant *p, gpointer u) {} + +static void on_load_response(GtkNativeDialog *d, int r, gpointer u) {} + +static void action_load(GSimpleAction *a, GVariant *p, gpointer u) {} + +static void activate(GtkApplication *app, gpointer user_data) { + GtkBuilderCScope *scope = GTK_BUILDER_CSCOPE(gtk_builder_cscope_new()); + gtk_builder_cscope_add_callback_symbol(scope, "on_tool_changed", + G_CALLBACK(on_tool_changed)); + + GtkBuilder *builder = gtk_builder_new(); + gtk_builder_set_scope(builder, GTK_BUILDER_SCOPE(scope)); + g_object_unref(scope); + gtk_builder_add_from_resource(builder, "/org/spaces/osnotes/notes.ui", NULL); + + main_window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window")); + canvas = GTK_WIDGET(gtk_builder_get_object(builder, "canvas")); + + const GActionEntry app_entries[] = { + {"save", action_save, NULL, NULL, NULL}, + {"load", action_load, NULL, NULL, NULL}, + {"export", action_export, NULL, NULL, NULL}}; + g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries, + G_N_ELEMENTS(app_entries), app); + + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(canvas), on_draw, NULL, NULL); + + GtkEventController *motion = gtk_event_controller_motion_new(); + g_signal_connect(motion, "motion", G_CALLBACK(on_motion), NULL); + gtk_widget_add_controller(canvas, motion); + + GtkGesture *click = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click), 0); + g_signal_connect(click, "pressed", G_CALLBACK(on_pressed), NULL); + g_signal_connect(click, "released", G_CALLBACK(on_released), NULL); + gtk_widget_add_controller(canvas, GTK_EVENT_CONTROLLER(click)); + + gtk_window_set_application(GTK_WINDOW(main_window), app); + gtk_window_present(GTK_WINDOW(main_window)); +} + +int main(int argc, char **argv) { + GtkApplication *app = + gtk_application_new("org.spaces.osnotes", G_APPLICATION_DEFAULT_FLAGS); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + int status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + return status; +} + +#pragma GCC diagnostic pop diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..c6d9d58 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,13 @@ +# Compile the resources into a C file +res = gnome.compile_resources( + 'osnotes-resources', + 'resources.xml', + source_dir: '.', # Look for .ui and .css in the current directory +) + +executable('osn', + 'main.c', + res, # Add the compiled resource file to the sources + dependencies : [gtk_dep, libevdev_dep, layer_shell_dep], + export_dynamic : true, + install : true) diff --git a/src/notes.ui b/src/notes.ui new file mode 100644 index 0000000..bd46443 --- /dev/null +++ b/src/notes.ui @@ -0,0 +1,99 @@ + + +

+
+ + + + + + + + + + Export to PNG + app.export + +
+
+ + + OSNotes + + + vertical + + + + FALSE + + + + 6 + + + + + document-edit-symbolic + TRUE + Pen + + + + + + + edit-select-symbolic + Highlighter + btn_pen + + + + + + + edit-clear-symbolic + Eraser + btn_pen + + + + + + + + + open-menu-symbolic + main_menu + + + + + + + + + + + + + + + + + + + + + + TRUE + TRUE + TRUE + + + + + + + + diff --git a/src/resources.xml b/src/resources.xml new file mode 100644 index 0000000..dde5e5c --- /dev/null +++ b/src/resources.xml @@ -0,0 +1,7 @@ + + + + notes.ui + style.css + + diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..533dfd8 --- /dev/null +++ b/src/style.css @@ -0,0 +1,5 @@ +#canvas_box, +#canvas_box > * { + background-color: black; + color: white; +}