diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index cd8f40d..5b5a913 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -1,6 +1,5 @@ on: push: - branches: [ "master" ] pull_request: workflow_dispatch: name: Snap diff --git a/data/ascii-draw.gresource.xml b/data/ascii-draw.gresource.xml index 1bc2341..43c7b69 100644 --- a/data/ascii-draw.gresource.xml +++ b/data/ascii-draw.gresource.xml @@ -21,5 +21,6 @@ icons/16x16/actions/sidebar-show-right-symbolic.svg icons/16x16/actions/line-style-symbolic.svg icons/16x16/actions/edit-undo-symbolic.svg + icons/16x16/actions/fill-tool-symbolic.svg diff --git a/data/gtk/help-overlay.ui b/data/gtk/help-overlay.ui index 4604f2b..e7b3f36 100644 --- a/data/gtk/help-overlay.ui +++ b/data/gtk/help-overlay.ui @@ -122,6 +122,12 @@ app.move-tool + + + Fill + app.fill-tool + + diff --git a/data/icons/16x16/actions/fill-tool-symbolic.svg b/data/icons/16x16/actions/fill-tool-symbolic.svg new file mode 100644 index 0000000..d96197e --- /dev/null +++ b/data/icons/16x16/actions/fill-tool-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/io.github.nokse22.asciidraw.metainfo.xml.in b/data/io.github.nokse22.asciidraw.metainfo.xml.in index e979753..c92507a 100644 --- a/data/io.github.nokse22.asciidraw.metainfo.xml.in +++ b/data/io.github.nokse22.asciidraw.metainfo.xml.in @@ -7,20 +7,22 @@ Sketch anything using characters -

This app lets you draw diagrams, tables, tree view, art and more using only characters. -There are many stiles to choose from and multiple tools available to use such as:

+

Draw diagrams, tables, tree view, art and more using only characters. +There are many tools and features available such as:

+ You can also organize characters into custom palettes.
@@ -44,6 +46,13 @@ There are many stiles to choose from and multiple tools available to use such as + + +

Added a tooltip to know the name and code of a character

+

Added Flood fill tool

+

Removed soft hyphen character

+
+

Split Extended Latin palette in ASCII and Extended ASCII

diff --git a/data/ui/window.ui b/data/ui/window.ui index 15f229b..f3cc8c5 100644 --- a/data/ui/window.ui +++ b/data/ui/window.ui @@ -691,6 +691,16 @@ + + + flat + rectangle_button + center + fill-tool-symbolic + Fill Ctrl+Shift+F + + + True diff --git a/src/canvas.py b/src/canvas.py index 790f5be..b03de63 100644 --- a/src/canvas.py +++ b/src/canvas.py @@ -74,6 +74,7 @@ def __init__(self, _styles, _flip): self.click_gesture = Gtk.GestureClick() self.click_gesture.set_propagation_phase(Gtk.PropagationPhase.CAPTURE) + self.click_gesture.set_button(0) self.drawing_area.add_controller(self.click_gesture) self.zoom_gesture = Gtk.GestureZoom() diff --git a/src/main.py b/src/main.py index 063f366..cb9f933 100644 --- a/src/main.py +++ b/src/main.py @@ -59,6 +59,7 @@ def __init__(self): self.create_action('eraser-tool', self.select_eraser_tool, ['e']) self.create_action('picker-tool', self.select_picker_tool, ['p']) self.create_action('move-tool', self.select_move_tool, ['m']) + self.create_action('fill-tool', self.select_fill_tool, ['f']) self.create_action('new-palette', self.on_new_palette_action) self.create_action('open-palette-folder', self.on_open_palette_folder_action) @@ -170,7 +171,7 @@ def on_about_action(self, *args): application_name=_("ASCII Draw"), application_icon='io.github.nokse22.asciidraw', developer_name='Nokse', - version='0.3.3', + version='0.3.4', website='https://github.com/Nokse22/ascii-draw', issue_url='https://github.com/Nokse22/ascii-draw/issues', developers=['Nokse'], @@ -180,7 +181,7 @@ def on_about_action(self, *args): about.set_translator_credits(_("translator-credits")) about.present(self.props.active_window) - def on_preferences_action(self, widget, _): + def on_preferences_action(self, *args): """Callback for the app.preferences action.""" print('app.preferences action activated') @@ -253,42 +254,45 @@ def on_save_changes(self, dialog, task, *args): elif response == "discard": self.quit() - def select_rectangle_tool(self, widget, _): + def select_rectangle_tool(self, *args): self.win.select_rectangle_tool() - def select_filled_rectangle_tool(self, widget, _): + def select_filled_rectangle_tool(self, *args): self.win.select_filled_rectangle_tool() - def select_line_tool(self, widget, _): + def select_line_tool(self, *args): self.win.select_line_tool() - def select_text_tool(self, widget, _): + def select_text_tool(self, *args): self.win.select_text_tool() - def select_table_tool(self, widget, _): + def select_table_tool(self, *args): self.win.select_table_tool() - def select_tree_tool(self, widget, _): + def select_tree_tool(self, *args): self.win.select_tree_tool() - def select_free_tool(self, widget, _): + def select_free_tool(self, *args): self.win.select_free_tool() - def select_eraser_tool(self, widget, _): + def select_eraser_tool(self, *args): self.win.select_eraser_tool() - def select_arrow_tool(self, widget, _): + def select_arrow_tool(self, *args): self.win.select_arrow_tool() - def select_free_line_tool(self, widget, _): + def select_free_line_tool(self, *args): self.win.select_free_line_tool() - def select_picker_tool(self, widget, _): + def select_picker_tool(self, *args): self.win.select_picker_tool() - def select_move_tool(self, widget, _): + def select_move_tool(self, *args): self.win.select_move_tool() + def select_fill_tool(self, *args): + self.win.select_fill_tool() + def main(version): """The application's entry point.""" app = AsciiDrawApplication() diff --git a/src/tools/__init__.py b/src/tools/__init__.py index ab46d30..5ecd47c 100644 --- a/src/tools/__init__.py +++ b/src/tools/__init__.py @@ -8,3 +8,4 @@ from .picker import Picker from .eraser import Eraser from .tree import Tree +from .flood_fill import Fill diff --git a/src/tools/flood_fill.py b/src/tools/flood_fill.py new file mode 100644 index 0000000..ad0970e --- /dev/null +++ b/src/tools/flood_fill.py @@ -0,0 +1,102 @@ +# flood_fill.py +# +# Copyright 2023 Nokse +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gi.repository import Adw +from gi.repository import Gtk +from gi.repository import Gdk, Gio, GObject + +class Fill(GObject.GObject): + def __init__(self, _canvas, *args, **kwargs): + super().__init__(*args, **kwargs) + self.canvas = _canvas + + self._active = False + + self.canvas.click_gesture.connect("pressed", self.on_click_pressed) + self.canvas.click_gesture.connect("released", self.on_click_released) + self.canvas.click_gesture.connect("stopped", self.on_click_stopped) + + self.start_x = 0 + self.start_y = 0 + + self.x_mul = 12 + self.y_mul = 24 + + self.end_x = 0 + self.end_y = 0 + + self._size = 1 + + @GObject.Property(type=bool, default=False) + def active(self): + return self._active + + @active.setter + def active(self, value): + self._active = value + self.notify('active') + + def on_click_pressed(self, click, arg, x, y): + if not self._active: return + x_char = int(x / self.x_mul) + y_char = int(y / self.y_mul) + + self.canvas.add_undo_action(_("Fill")) + + button = click.get_current_button() + + if button == 1: + flood_fill(self.canvas, x_char, y_char, self.canvas.get_selected_char()) + elif button == 3: + flood_fill(self.canvas, x_char, y_char, self.canvas.get_unselected_char()) + + self.canvas.update() + + def on_click_stopped(self, click): + if not self._active: return + pass + + def on_click_released(self, click, arg, x, y): + if not self._active: return + pass + +def flood_fill(canvas, start_x, start_y, replacement_char): + target_char = canvas.get_char_at(start_x, start_y) + + if target_char == replacement_char: + return + + rows, cols = canvas.get_canvas_size() + + stack = [(start_x, start_y)] + + while stack: + x, y = stack.pop() + + if canvas.get_char_at(x, y) == target_char: + canvas.set_char_at(x, y, replacement_char, True) + + if x > 0: + stack.append((x - 1, y)) + if x < rows - 1: + stack.append((x + 1, y)) + if y > 0: + stack.append((x, y - 1)) + if y < cols - 1: + stack.append((x, y + 1)) diff --git a/src/window.py b/src/window.py index fb9df0d..a3a09c9 100644 --- a/src/window.py +++ b/src/window.py @@ -63,6 +63,7 @@ class AsciiDrawWindow(Adw.ApplicationWindow): table_button = Gtk.Template.Child() picker_button = Gtk.Template.Child() eraser_button = Gtk.Template.Child() + fill_button = Gtk.Template.Child() eraser_adjustment = Gtk.Template.Child() line_arrow_switch = Gtk.Template.Child() @@ -185,6 +186,9 @@ def __init__(self, **kwargs): self.tree_tool.bind_property('active', self.tree_button, 'active', GObject.BindingFlags.BIDIRECTIONAL) self.tree_tool.bind_property('text', self.tree_text_entry_buffer, 'text', GObject.BindingFlags.BIDIRECTIONAL) + self.fill_tool = Fill(self.canvas) + self.fill_tool.bind_property('active', self.fill_button, 'active', GObject.BindingFlags.BIDIRECTIONAL) + prev_btn = None for style in self.styles: @@ -679,6 +683,14 @@ def on_choose_eraser(self, btn): self.sidebar_stack.set_visible_child_name("eraser_page") self.canvas.clear_preview() + @Gtk.Template.Callback("on_choose_fill") + def on_choose_fill(self, btn): + print("fill") + current_sidebar = self.sidebar_stack.get_visible_child_name() + if current_sidebar != "character_page" and current_sidebar != "style_page": + self.sidebar_stack.set_visible_child_name("character_page") + self.canvas.clear_preview() + def new_palette_from_canvas(self): content = self.canvas.get_content() content = content.replace('\n', '') @@ -790,3 +802,6 @@ def select_picker_tool(self): def select_move_tool(self): self.move_button.set_active(True) + + def select_fill_tool(self): + self.fill_button.set_active(True)