From 7d797f838bcf1ba5a9c7e8a1c3c103b23844d697 Mon Sep 17 00:00:00 2001 From: lisham2000 Date: Tue, 26 Nov 2024 13:36:55 +0000 Subject: [PATCH] Add graphics capabilities to handle new features in Inspector.py --- nion/swift/model/Graphics.py | 86 +++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/nion/swift/model/Graphics.py b/nion/swift/model/Graphics.py index 0bd956e0..2d47ae01 100755 --- a/nion/swift/model/Graphics.py +++ b/nion/swift/model/Graphics.py @@ -36,7 +36,7 @@ class DrawingContextLike(typing.Protocol): fill_style: typing.Optional[typing.Union[str, _LinearGradientLike]] font: typing.Optional[str] - line_width: float + line_width: typing.Optional[float] line_dash: typing.Optional[int] stroke_style: typing.Optional[str] text_align: typing.Optional[str] @@ -461,7 +461,7 @@ def draw_rect_marker(ctx: DrawingContextLike, r: Geometry.FloatRect, fill_style: draw_marker(ctx, r.bottom_left, fill_style) -def draw_ellipse_graphic(ctx: DrawingContextLike, center: Geometry.FloatPoint, size: Geometry.FloatSize, rotation: float, is_selected: bool, stroke_style: typing.Optional[str], fill_style: typing.Optional[str]) -> None: +def draw_ellipse_graphic(ctx: DrawingContextLike, center: Geometry.FloatPoint, size: Geometry.FloatSize, rotation: float, is_selected: bool, stroke_style: typing.Optional[str],stroke_width:typing.Optional[float], fill_style: typing.Optional[str]) -> None: rect = Geometry.FloatRect.from_center_and_size(center, size) origin = rect.origin top_left = rect.top_left @@ -474,14 +474,14 @@ def draw_ellipse_graphic(ctx: DrawingContextLike, center: Geometry.FloatPoint, s ctx.translate(center.x, center.y) ctx.rotate(-rotation) ctx.translate(-center.x, -center.y) - ctx.line_width = 1 + ctx.line_width = stroke_width or 1 draw_ellipse(ctx, origin + size / 2, size, stroke_style, fill_style) ctx.begin_path() - ctx.move_to(center.x, rect.top + 10) - ctx.line_to(center.x, rect.top + 2) - draw_arrow(ctx, Geometry.FloatPoint(y=rect.top + 10, x=center.x), Geometry.FloatPoint(y=rect.top + 2, x=center.x), arrow_size=4) + ctx.move_to(center.x, rect.top + 10 + (.5 * int(stroke_width or 1))) + ctx.line_to(center.x, rect.top + 2 + (.5 * int(stroke_width or 1))) + draw_arrow(ctx, Geometry.FloatPoint(y=rect.top + 10 + (.5 * int(stroke_width or 1)), x=center.x), Geometry.FloatPoint(y=rect.top + 2 + (.5 * int(stroke_width or 1)) , x=center.x), arrow_size=4) ctx.close_path() - ctx.line_width = 1 + ctx.line_width = 1 ctx.stroke_style = stroke_style ctx.stroke() ctx.fill_style = fill_style @@ -638,8 +638,11 @@ def __init__(self, type: str) -> None: self.define_property("graphic_id", None, changed=self._property_changed, validate=lambda s: str(s) if s else None, hidden=True) self.define_property("source_specifier", changed=self.__source_specifier_changed, key="source_uuid", hidden=True) self.define_property("stroke_color", None, changed=self._property_changed, hidden=True) + self.define_property("stroke_width", None, changed=self._property_changed, hidden=True) self.define_property("fill_color", None, changed=self._property_changed, hidden=True) self.define_property("label", changed=self._property_changed, validate=lambda s: str(s) if s else None, hidden=True) + self.define_property("is_stroke_enabled", True, changed=self._property_changed, hidden=True) + self.define_property("is_fill_enabled", True, changed=self._property_changed, hidden=True) self.define_property("is_position_locked", False, changed=self._property_changed, hidden=True) self.define_property("is_shape_locked", False, changed=self._property_changed, hidden=True) self.define_property("is_bounds_constrained", False, changed=self._property_changed, hidden=True) @@ -679,6 +682,14 @@ def stroke_color(self) -> typing.Optional[str]: def stroke_color(self, value: typing.Optional[str]) -> None: self._set_persistent_property_value("stroke_color", value) + @property + def stroke_width(self) -> typing.Optional[float]: + return typing.cast(typing.Optional[float], self._get_persistent_property_value("stroke_width")) + + @stroke_width.setter + def stroke_width(self, value: typing.Optional[float]) -> None: + self._set_persistent_property_value("stroke_width", value) + @property def fill_color(self) -> typing.Optional[str]: return typing.cast(typing.Optional[str], self._get_persistent_property_value("fill_color")) @@ -695,6 +706,22 @@ def label(self) -> typing.Optional[str]: def label(self, value: typing.Optional[str]) -> None: self._set_persistent_property_value("label", value) + @property + def is_stroke_enabled(self) -> bool: + return typing.cast(bool, self._get_persistent_property_value("is_stroke_enabled")) + + @is_stroke_enabled.setter + def is_stroke_enabled(self, value: bool) -> None: + self._set_persistent_property_value("is_stroke_enabled", value) + + @property + def is_fill_enabled(self) -> bool: + return typing.cast(bool, self._get_persistent_property_value("is_fill_enabled")) + + @is_fill_enabled.setter + def is_fill_enabled(self, value: bool) -> None: + self._set_persistent_property_value("is_fill_enabled", value) + @property def is_position_locked(self) -> bool: return typing.cast(bool, self._get_persistent_property_value("is_position_locked")) @@ -753,9 +780,12 @@ def mime_data_dict(self) -> Persistence.PersistentDictType: return { "type": self.type, "stroke_color": self.stroke_color, + "stroke_width": self.stroke_width, "fill_color": self.fill_color, "label": self.label, "is_position_locked": self.is_position_locked, + "is_stroke_enabled": self.is_stroke_enabled, + "is_fill_enabled": self.is_fill_enabled, "is_shape_locked": self.is_shape_locked, "is_bounds_constrained": self.is_bounds_constrained, } @@ -770,8 +800,11 @@ def write_to_dict(self) -> Persistence.PersistentDictType: def read_from_mime_data(self, graphic_dict: Persistence.PersistentDictType) -> None: self.stroke_color = graphic_dict.get("stroke_color", self.stroke_color) + self.stroke_color = graphic_dict.get("stroke_width", self.stroke_width) self.fill_color = graphic_dict.get("fill_color", self.fill_color) self.label = graphic_dict.get("label", self.label) + self.is_stroke_enabled = graphic_dict.get("stroke_enabled",self.is_stroke_enabled) + self.is_stroke_enabled = graphic_dict.get("stroke_enabled", self.is_fill_enabled) self.is_position_locked = graphic_dict.get("is_position_locked", self.is_position_locked) self.is_shape_locked = graphic_dict.get("is_shape_locked", self.is_shape_locked) self.is_bounds_constrained = graphic_dict.get("is_bounds_constrained", self.is_bounds_constrained) @@ -804,6 +837,11 @@ def used_role(self) -> typing.Optional[str]: @property def used_stroke_style(self) -> typing.Optional[str]: + if not self.is_stroke_enabled: + if not self.is_fill_enabled: + return self._default_stroke_color + else: + return None if self.stroke_color: return self.stroke_color if self.used_role == "fourier_mask": @@ -814,6 +852,8 @@ def used_stroke_style(self) -> typing.Optional[str]: @property def used_fill_style(self) -> typing.Optional[str]: + if not self.is_fill_enabled: + return None if self.fill_color: return self.fill_color if self.used_role == "fourier_mask": @@ -1151,7 +1191,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map ctx.line_to(center.x, rect.top + 2) draw_arrow(ctx, Geometry.FloatPoint(y=rect.top + 10, x=center.x), Geometry.FloatPoint(y=rect.top + 2, x=center.x), arrow_size=4) ctx.close_path() - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.stroke() ctx.fill_style = self.used_fill_style @@ -1232,10 +1272,11 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map rotation = self.rotation stroke_style = self.used_stroke_style fill_style = self.used_fill_style + stroke_width= self.stroke_width or 1 bounds = Geometry.FloatRect.make(self.bounds) center = mapping.map_point_image_norm_to_widget(bounds.center) size = mapping.map_size_image_norm_to_widget(bounds.size) - draw_ellipse_graphic(ctx, center, size, rotation, is_selected, stroke_style, fill_style) + draw_ellipse_graphic(ctx, center, size, rotation, is_selected, stroke_style, stroke_width, fill_style) self.draw_label(ctx, ui_settings, mapping) def label_position(self, mapping: CoordinateMappingLike, font_metrics: UISettings.FontMetrics, padding: float) -> typing.Optional[Geometry.FloatPoint]: @@ -1542,7 +1583,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map draw_arrow(ctx, p2, p1) if self.end_arrow_enabled: draw_arrow(ctx, p1, p2) - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.stroke() if is_selected: @@ -1600,7 +1641,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map draw_arrow(ctx, p2, p1) if self.end_arrow_enabled: draw_arrow(ctx, p1, p2) - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.stroke() length = math.sqrt(math.pow(p2[0] - p1[0],2) + math.pow(p2[1] - p1[1], 2)) @@ -1615,7 +1656,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map ctx.line_to(p2[1] - dy * half_width, p2[0] + dx * half_width) ctx.line_to(p1[1] - dy * half_width, p1[0] + dx * half_width) ctx.close_path() - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.line_dash = 2 ctx.stroke_style = self.used_stroke_style ctx.stroke() @@ -1632,7 +1673,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map ctx.line_to(pa[0] - dy * interval_marker_half_width, pa[1] + dx * interval_marker_half_width) ctx.move_to(pb[0] + dy * interval_marker_half_width, pb[1] - dx * interval_marker_half_width) ctx.line_to(pb[0] - dy * interval_marker_half_width, pb[1] + dx * interval_marker_half_width) - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = color ctx.stroke() if is_selected: @@ -1758,7 +1799,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map ctx.line_to(p.x, p.y - inner_size) ctx.move_to(p.x, p.y + inner_size) ctx.line_to(p.x, p.y + cross_hair_size) - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.stroke() self.draw_label(ctx, ui_settings, mapping) @@ -2150,16 +2191,17 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map stroke_style = self.used_stroke_style fill_style = self.used_fill_style rotation = self.rotation + stroke_width = self.stroke_width or 1 bounds = Geometry.FloatRect.make(self.bounds) origin = mapping.calibrated_origin_widget center = origin + mapping.map_size_image_norm_to_widget(bounds.center.as_size()) size = mapping.map_size_image_norm_to_widget(bounds.size) - draw_ellipse_graphic(ctx, center, size, rotation, is_selected, stroke_style, fill_style) + draw_ellipse_graphic(ctx, center, size, rotation, is_selected, stroke_style, stroke_width, fill_style) with ctx.saver(): ctx.translate(origin.x, origin.y) ctx.rotate(math.pi) ctx.translate(-origin.x, -origin.y) - draw_ellipse_graphic(ctx, center, size, rotation, is_selected, stroke_style, fill_style) + draw_ellipse_graphic(ctx, center, size, rotation, is_selected, stroke_style, stroke_width, fill_style) self.draw_label(ctx, ui_settings, mapping) def label_position(self, mapping: CoordinateMappingLike, font_metrics: UISettings.FontMetrics, padding: float) -> typing.Optional[Geometry.FloatPoint]: @@ -2389,7 +2431,7 @@ def draw_mask(sign: float) -> None: ctx.line_to(corner.x, corner.y) ctx.line_to(pt2.x, pt2.y) ctx.close_path() - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.fill_style = self.used_fill_style ctx.fill() @@ -2553,7 +2595,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map radius_1_widget = mapping.map_size_image_norm_to_widget(Geometry.FloatSize(self.radius_1, self.radius_1)) radius_2_widget = mapping.map_size_image_norm_to_widget(Geometry.FloatSize(self.radius_2, self.radius_2)) with ctx.saver(): - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style draw_ellipse(ctx, center, Geometry.FloatSize(width=radius_1_widget[1] * 2, height=radius_1_widget[0] * 2), self.used_stroke_style, None) if is_selected: @@ -2562,7 +2604,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map draw_marker(ctx, Geometry.FloatPoint(center.y, center.x + radius_1_widget[1])) draw_marker(ctx, Geometry.FloatPoint(center.y, center.x - radius_1_widget[1])) if not self.mode == "low-pass" and not self.mode == "high-pass": - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style draw_ellipse(ctx, center, Geometry.FloatSize(width=radius_2_widget[1] * 2, height=radius_2_widget[0] * 2), self.used_stroke_style, None) if is_selected: @@ -2899,7 +2941,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map ctx.move_to(start_widget[1], start_widget[0]) ctx.line_to(u_pos_widget[1], u_pos_widget[0]) draw_arrow(ctx, start_widget, u_pos_widget) - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.stroke() ctx.fill_style = self.used_fill_style @@ -2909,7 +2951,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map ctx.move_to(start_widget[1], start_widget[0]) ctx.line_to(v_pos_widget[1], v_pos_widget[0]) draw_arrow(ctx, start_widget, v_pos_widget) - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.stroke() ctx.fill_style = self.used_fill_style @@ -2928,7 +2970,7 @@ def _draw(self, ctx: DrawingContextLike, ui_settings: UISettings.UISettings, map bounds = Geometry.FloatRect.from_tlbr(0, 0, 1, 1).inset(-radius, -radius) with ctx.saver(): - ctx.line_width = 1 + ctx.line_width = self.stroke_width or 1 ctx.stroke_style = self.used_stroke_style ctx.fill_style = self.used_fill_style mx = 0