Skip to content

Commit

Permalink
Add actions for closing polygons
Browse files Browse the repository at this point in the history
this concerns the 'shape' tool and the 'free_select' tool, where menu items now allow to close the shape even if you don't remember where was the first point
  • Loading branch information
maoschanz committed Oct 13, 2019
1 parent 835488f commit cc022a7
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 93 deletions.
2 changes: 1 addition & 1 deletion data/com.github.maoschanz.drawing.appdata.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<!-- </p> -->
<!-- </description> -->
<!-- </release> -->
<release version="0.4.8" date="2019-10-25">
<release version="0.4.8" date="2019-10-13">
<description>
<p>Version 0.4.8 features several minor bug fixes, and various new
translations.</p>
Expand Down
8 changes: 4 additions & 4 deletions src/selection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ def get_path_with_scroll(self):
if self.selection_path is None:
return None # TODO throw something goddammit
cairo_context = self._get_context()
delta_x = 0 - self.image.scroll_x + self.selection_x - self.temp_x
delta_y = 0 - self.image.scroll_y + self.selection_y - self.temp_y
delta_x = 0 - self.image.scroll_x + self.selection_x - self.temp_x # XXX UTILISATION DE TEMP
delta_y = 0 - self.image.scroll_y + self.selection_y - self.temp_y # XXX UTILISATION DE TEMP
for pts in self.selection_path:
if pts[1] is not ():
x = pts[1][0] + delta_x
Expand Down Expand Up @@ -187,8 +187,8 @@ def point_is_in_selection(self, tested_x, tested_y):
cairo_context = self._get_context()
for pts in self.selection_path:
if pts[1] is not ():
x = pts[1][0] + self.selection_x - self.temp_x
y = pts[1][1] + self.selection_y - self.temp_y
x = pts[1][0] + self.selection_x - self.temp_x # XXX UTILISATION DE TEMP
y = pts[1][1] + self.selection_y - self.temp_y # XXX UTILISATION DE TEMP
cairo_context.line_to(int(x), int(y))
return cairo_context.in_fill(tested_x, tested_y)

Expand Down
12 changes: 7 additions & 5 deletions src/tools/abstract_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,13 @@ def on_release_on_area(self, event, surface, event_x, event_y):
def on_draw(self, area, cairo_context):
if not self.accept_selection:
return
# Basic implementation, tools should do it better to fit their needs
if self.selection_is_active():
self.get_selection().show_selection_on_surface(cairo_context, True)
dragged_path = self.get_selection().get_path_with_scroll()
utilities_show_overlay_on_context(cairo_context, dragged_path, True)
# Basic implementation, in fact never executed becaus tools needing it
# will do it better to fit their needs
if not self.selection_is_active():
return
self.get_selection().show_selection_on_surface(cairo_context, True)
dragged_path = self.get_selection().get_path_with_scroll() # XXX
utilities_show_overlay_on_context(cairo_context, dragged_path, True)

############################################################################
################################################################################
Expand Down
2 changes: 1 addition & 1 deletion src/tools/canvas_tools/abstract_canvas_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def temp_preview(self, is_selection):
############################################################################

def on_draw(self, area, cairo_context):
pass # TODO FIXME pour l'instant pas d'overlay quand on modifie la sélection
pass

def get_deformed_surface(self, source_surface, p_xx, p_yx, p_xy, p_yy, p_x0, p_y0):
source_w = source_surface.get_width()
Expand Down
109 changes: 58 additions & 51 deletions src/tools/classic_tools/tool_shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,66 +15,69 @@ def __init__(self, window, **kwargs):
self._path = None

self.reset_temp_points()
self.selected_style_id = 'empty'
self.selected_style_label = _("Empty")
self.selected_join_id = cairo.LineJoin.ROUND
self.selected_shape_id = 'polygon'
self.selected_shape_label = _("Polygon")
self._style_id = 'empty'
self._style_label = _("Empty")
self._join_id = cairo.LineJoin.ROUND
self._shape_id = 'polygon'
self._shape_label = _("Polygon")

self.add_tool_action_enum('shape_type', self.selected_shape_id)
self.add_tool_action_enum('shape_filling', self.selected_style_id)
self.add_tool_action_enum('shape_type', self._shape_id)
self.add_tool_action_enum('shape_filling', self._style_id)
self.add_tool_action_simple('shape_close', self.force_close_polygon)
self.set_action_sensitivity('shape_close', False)

def reset_temp_points(self):
self.x_press = -1.0
self.y_press = -1.0
self.past_x = -1.0
self.past_y = -1.0
self.initial_x = -1.0
self.initial_y = -1.0

def set_filling_style(self):
state_as_string = self.get_option_value('shape_filling')
self.selected_style_id = state_as_string
self._style_id = state_as_string
if state_as_string == 'empty':
self.selected_style_label = _("Empty outline")
self._style_label = _("Empty outline")
elif state_as_string == 'filled':
self.selected_style_label = _("Main color")
self._style_label = _("Main color")
elif state_as_string == 'h-gradient':
self.selected_style_label = _("Horizontal gradient")
self._style_label = _("Horizontal gradient")
elif state_as_string == 'v-gradient':
self.selected_style_label = _("Vertical gradient")
self._style_label = _("Vertical gradient")
elif state_as_string == 'r-gradient':
self.selected_style_label = _("Radial gradient")
self._style_label = _("Radial gradient")
else:
self.selected_style_label = _("Secondary color")
self._style_label = _("Secondary color")

def set_active_shape(self, *args):
self.selected_shape_id = self.get_option_value('shape_type')
if self.selected_shape_id == 'rectangle':
self.selected_shape_label = _("Rectangle")
self.selected_join_id = cairo.LineJoin.MITER
elif self.selected_shape_id == 'oval':
self.selected_shape_label = _("Oval")
self.selected_join_id = cairo.LineJoin.ROUND
elif self.selected_shape_id == 'circle':
self.selected_shape_label = _("Circle")
self.selected_join_id = cairo.LineJoin.ROUND
elif self.selected_shape_id == 'polygon':
self.selected_shape_label = _("Polygon")
self.selected_join_id = cairo.LineJoin.MITER # BEVEL ?
self._shape_id = self.get_option_value('shape_type')
if self._shape_id == 'rectangle':
self._shape_label = _("Rectangle")
self._join_id = cairo.LineJoin.MITER
elif self._shape_id == 'oval':
self._shape_label = _("Oval")
self._join_id = cairo.LineJoin.ROUND
elif self._shape_id == 'circle':
self._shape_label = _("Circle")
self._join_id = cairo.LineJoin.ROUND
elif self._shape_id == 'polygon':
self._shape_label = _("Polygon")
self._join_id = cairo.LineJoin.MITER # BEVEL ?
else:
self.selected_shape_label = _("Free shape")
self.selected_join_id = cairo.LineJoin.ROUND
self._shape_label = _("Free shape")
self._join_id = cairo.LineJoin.ROUND

def get_options_label(self):
return _("Shape options")

def get_edition_status(self):
self.set_filling_style()
self.set_active_shape()
label = self.selected_shape_label + ' - ' + self.selected_style_label
if self.selected_shape_id == 'polygon' or self.selected_shape_id == 'freeshape':
label = self._shape_label + ' - ' + self._style_label
if self._shape_id == 'polygon' or self._shape_id == 'freeshape':
instruction = _("Click on the shape's first point to close it.")
# TODO action 'shape_close'
label = label + ' - ' + instruction
else:
self.set_action_sensitivity('shape_close', False)
return label

def give_back_control(self, preserve_selection):
Expand All @@ -89,48 +92,52 @@ def on_press_on_area(self, event, surface, event_x, event_y):
self.set_common_values(event)

def on_motion_on_area(self, event, surface, event_x, event_y):
if self.selected_shape_id == 'freeshape':
if self._shape_id == 'freeshape':
operation = self.add_point(event_x, event_y, True)
elif self.selected_shape_id == 'polygon':
elif self._shape_id == 'polygon':
operation = self.add_point(event_x, event_y, False)
else:
if self.selected_shape_id == 'rectangle':
if self._shape_id == 'rectangle':
self.build_rectangle(event_x, event_y)
elif self.selected_shape_id == 'oval':
elif self._shape_id == 'oval':
self.draw_oval(event_x, event_y)
elif self.selected_shape_id == 'circle':
elif self._shape_id == 'circle':
self.draw_circle(event_x, event_y)
operation = self.build_operation(self._path)
self.do_tool_operation(operation)

def on_release_on_area(self, event, surface, event_x, event_y):
if self.selected_shape_id == 'freeshape' or self.selected_shape_id == 'polygon':
if self._shape_id == 'freeshape' or self._shape_id == 'polygon':
operation = self.add_point(event_x, event_y, True)
self.set_action_sensitivity('shape_close', not operation['closed'])
if operation['closed']:
self.apply_operation(operation)
self.reset_temp_points()
else:
self.do_tool_operation(operation)
self.non_destructive_show_modif()
else:
if self.selected_shape_id == 'rectangle':
if self._shape_id == 'rectangle':
self.build_rectangle(event_x, event_y)
elif self.selected_shape_id == 'oval':
elif self._shape_id == 'oval':
self.draw_oval(event_x, event_y)
elif self.selected_shape_id == 'circle':
elif self._shape_id == 'circle':
self.draw_circle(event_x, event_y)
operation = self.build_operation(self._path)
self.apply_operation(operation)
self.reset_temp_points()

############################################################################

def force_close_polygon(self, *args):
self.on_release_on_area(None, None, self.initial_x, self.initial_y)

def add_point(self, event_x, event_y, memorize):
"""Add a point to a shape (used by both freeshape and polygon)."""
cairo_context = cairo.Context(self.get_surface())
if self.past_x == -1.0:
if self.initial_x == -1.0:
# print('init polygon')
(self.past_x, self.past_y) = (self.x_press, self.y_press)
(self.initial_x, self.initial_y) = (self.x_press, self.y_press)
cairo_context.move_to(self.x_press, self.y_press)
self._path = cairo_context.copy_path()
else:
Expand All @@ -148,10 +155,10 @@ def add_point(self, event_x, event_y, memorize):
return operation

def should_close_shape(self, event_x, event_y):
if self.past_x == -1.0 or self.past_y == -1.0:
if self.initial_x == -1.0 or self.initial_y == -1.0:
return False
delta_x = max(event_x, self.past_x) - min(event_x, self.past_x)
delta_y = max(event_y, self.past_y) - min(event_y, self.past_y)
delta_x = max(event_x, self.initial_x) - min(event_x, self.initial_x)
delta_y = max(event_y, self.initial_y) - min(event_y, self.initial_y)
return (delta_x < self.tool_width and delta_y < self.tool_width)

############################################################################
Expand Down Expand Up @@ -191,10 +198,10 @@ def build_operation(self, cairo_path):
'rgba_main': self.main_color,
'rgba_secd': self.secondary_color,
'operator': cairo.Operator.OVER,
'line_join': self.selected_join_id,
'line_join': self._join_id,
'line_width': self.tool_width,
'filling': self.selected_style_id,
'smooth': (self.selected_shape_id == 'freeshape'),
'filling': self._style_id,
'smooth': (self._shape_id == 'freeshape'),
'closed': True,
'path': cairo_path
}
Expand Down
71 changes: 41 additions & 30 deletions src/tools/selection_tools/abstract_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from .abstract_tool import ToolTemplate
from .bottombar import DrawingAdaptativeBottomBar

from .utilities import utilities_show_overlay_on_context

class AbstractSelectionTool(ToolTemplate):
__gtype_name__ = 'AbstractSelectionTool'
# x_press = 0
Expand Down Expand Up @@ -92,23 +94,45 @@ def on_tool_unselected(self, *args):
############################################################################
############################################################################

def get_press_behavior(self):
if self.selection_is_active():
if self.get_selection().point_is_in_selection(self.x_press, self.y_press):
return 'drag'
#else: # superflu
# return 'cancel'
else:
def get_press_behavior(self, event):
if event.button == 3:
return 'menu'
elif not self.selection_is_active():
return 'define'
return 'cancel'
elif self.get_selection().point_is_in_selection(self.x_press, self.y_press):
return 'drag'
else:
return 'cancel'

def press_define(self, event_x, event_y):
pass # implemented by actual tools

def motion_define(self, event_x, event_y):
pass # implemented by actual tools

def release_define(self, surface, event_x, event_y):
pass # implemented by actual tools

def drag_to(self, event_x, event_y):
x = self.get_selection().selection_x
y = self.get_selection().selection_y
self.future_x = x + event_x - self.x_press
self.future_y = y + event_y - self.y_press
self.operation_type = 'op-drag'
operation = self.build_operation()
self.do_tool_operation(operation)
self.operation_type = 'op-define'

def preview_drag_to(self, event_x, event_y):
pass # TODO ??

############################################################################
# Signal callbacks implementations #########################################

def on_press_on_area(self, event, surface, event_x, event_y):
self.x_press = event_x
self.y_press = event_y
self.behavior = self.get_press_behavior()
self.behavior = self.get_press_behavior(event)
# print('press', self.behavior, AbstractSelectionTool.future_path)
if self.behavior == 'drag':
self.cursor_name = 'grabbing'
Expand Down Expand Up @@ -148,27 +172,14 @@ def on_release_on_area(self, event, surface, event_x, event_y):
elif self.behavior == 'drag':
self.drag_to(event_x, event_y)

def press_define(self, event_x, event_y):
pass # implemented by actual tools

def motion_define(self, event_x, event_y):
pass # implemented by actual tools

def release_define(self, surface, event_x, event_y):
pass # implemented by actual tools

def drag_to(self, event_x, event_y):
x = self.get_selection().selection_x
y = self.get_selection().selection_y
self.future_x = x + event_x - self.x_press
self.future_y = y + event_y - self.y_press
self.operation_type = 'op-drag'
operation = self.build_operation()
self.do_tool_operation(operation)
self.operation_type = 'op-define'

def preview_drag_to(self, event_x, event_y):
pass # TODO
def on_draw(self, area, cairo_context):
if not self.selection_is_active():
return
self.get_selection().show_selection_on_surface(cairo_context, True)
dragged_path = self.get_selection().get_path_with_scroll() # TODO i
# should give it something corresponding temporary selection_x/y when
# the selection is moving. Method not really use elsewhere.
utilities_show_overlay_on_context(cairo_context, dragged_path, True)

############################################################################
# Path management ##########################################################
Expand Down
18 changes: 17 additions & 1 deletion src/tools/selection_tools/free_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,21 @@ def __init__(self, window, **kwargs):
self.closing_precision = 10
self.closing_x = 0.0
self.closing_y = 0.0
self.add_tool_action_simple('selection_close', self.force_close_polygon)
self.set_action_sensitivity('selection_close', False)

def on_tool_selected(self, *args):
super().on_tool_selected()

def on_tool_unselected(self, *args):
super().on_tool_unselected()
self.set_action_sensitivity('selection_close', False)

############################################################################

def press_define(self, event_x, event_y):
self.draw_polygon(event_x, event_y)
self.set_action_sensitivity('selection_close', True)

def motion_define(self, event_x, event_y):
self.draw_polygon(event_x, event_y)
Expand All @@ -28,12 +40,16 @@ def release_define(self, surface, event_x, event_y):
operation = self.build_operation()
self.apply_operation(operation)
# self.get_selection().show_popover()
# self.set_selection_has_been_used(False) # TODO
# self.set_selection_has_been_used(False) # TODO ?
self.set_action_sensitivity('selection_close', False)
else:
return # without updating the surface so the path is visible

############################################################################

def force_close_polygon(self, *args):
self.release_define(None, self.closing_x, self.closing_y)

def draw_polygon(self, event_x, event_y):
"""This method is specific to the 'free selection' mode."""
cairo_context = cairo.Context(self.get_surface())
Expand Down
Loading

1 comment on commit cc022a7

@maoschanz
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was the issue #21

Please sign in to comment.