diff --git a/icons/info.png b/icons/info.png new file mode 100644 index 000000000..35d4e94c9 Binary files /dev/null and b/icons/info.png differ diff --git a/icons/info.svg b/icons/info.svg new file mode 100644 index 000000000..1053b0a3d --- /dev/null +++ b/icons/info.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons/info_dark.png b/icons/info_dark.png new file mode 100644 index 000000000..63f6dccf4 Binary files /dev/null and b/icons/info_dark.png differ diff --git a/lib/gui/lettering/main_panel.py b/lib/gui/lettering/main_panel.py index eb3d61a4f..a99fe46dd 100644 --- a/lib/gui/lettering/main_panel.py +++ b/lib/gui/lettering/main_panel.py @@ -266,6 +266,7 @@ class LetteringPanel(wx.Panel): elif filter_size != 0: self.options_panel.scale_spinner.SetValue(int(filter_size / font.size * 100)) self.settings['scale'] = self.options_panel.scale_spinner.GetValue() + self.update_preview() def resize(self, event=None): description = self.options_panel.font_description.GetLabel().replace("\n", " ") diff --git a/lib/gui/simulator/__init__.py b/lib/gui/simulator/__init__.py index 4ccc745bf..1307586e2 100644 --- a/lib/gui/simulator/__init__.py +++ b/lib/gui/simulator/__init__.py @@ -3,6 +3,7 @@ # Copyright (c) 2024 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. +from .design_info import DesignInfoDialog from .simulator_preferences import SimulatorPreferenceDialog from .simulator_slider import SimulatorSlider from .control_panel import ControlPanel diff --git a/lib/gui/simulator/design_info.py b/lib/gui/simulator/design_info.py new file mode 100644 index 000000000..86d4e3d74 --- /dev/null +++ b/lib/gui/simulator/design_info.py @@ -0,0 +1,69 @@ +# Authors: see git history +# +# Copyright (c) 2024 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +import wx + +from ...i18n import _ + + +class DesignInfoDialog(wx.Dialog): + """A dialog to show design info + """ + + def __init__(self, *args, **kwargs): + super(DesignInfoDialog, self).__init__(*args, **kwargs) + self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE) + + self.view_panel = self.GetParent() + self.drawing_panel = self.view_panel.drawing_panel + + sizer = wx.BoxSizer(wx.VERTICAL) + info_sizer = wx.FlexGridSizer(6, 2, 5, 5) + + dimensions_label = wx.StaticText(self, label=_("Design dimensions (mm)")) + self.dimensions = wx.StaticText(self) + + num_stitches_label = wx.StaticText(self, label=_('# Stitches')) + self.num_stitches = wx.StaticText(self) + + num_color_changes_label = wx.StaticText(self, label=_("# Color Changes")) + self.num_color_changes = wx.StaticText(self) + + num_jumps_label = wx.StaticText(self, label=_("# Jumps")) + self.num_jumps = wx.StaticText(self) + + num_trims_label = wx.StaticText(self, label=_("# Trims")) + self.num_trims = wx.StaticText(self) + + num_stops_label = wx.StaticText(self, label=_("# Stops")) + self.num_stops = wx.StaticText(self) + + info_sizer.Add(dimensions_label, 0, wx.ALL, 10) + info_sizer.Add(self.dimensions, 0, wx.EXPAND | wx.ALL, 10) + info_sizer.Add(num_stitches_label, 0, wx.ALL, 10) + info_sizer.Add(self.num_stitches, 0, wx.EXPAND | wx.ALL, 10) + info_sizer.Add(num_color_changes_label, 0, wx.ALL, 10) + info_sizer.Add(self.num_color_changes, 0, wx.EXPAND | wx.ALL, 10) + info_sizer.Add(num_jumps_label, 0, wx.ALL, 10) + info_sizer.Add(self.num_jumps, 0, wx.EXPAND | wx.ALL, 10) + info_sizer.Add(num_trims_label, 0, wx.ALL, 10) + info_sizer.Add(self.num_trims, 0, wx.EXPAND | wx.ALL, 10) + info_sizer.Add(num_stops_label, 0, wx.ALL, 10) + info_sizer.Add(self.num_stops, 0, wx.EXPAND | wx.ALL, 10) + + sizer.Add(info_sizer, 1, wx.ALL, 10) + self.SetSizerAndFit(sizer) + self.update() + + def update(self): + if not self.drawing_panel.loaded: + return + self.dimensions.SetLabel("{:.2f} x {:.2f}".format(self.drawing_panel.dimensions_mm[0], self.drawing_panel.dimensions_mm[1])) + self.num_stitches.SetLabel(f"{self.drawing_panel.num_stitches}") + self.num_color_changes.SetLabel(f"{self.drawing_panel.num_color_changes}") + self.num_jumps.SetLabel(f"{self.drawing_panel.num_jumps}") + self.num_trims.SetLabel(f"{self.drawing_panel.num_trims}") + self.num_stops.SetLabel(f"{self.drawing_panel.num_stops}") + self.Fit() diff --git a/lib/gui/simulator/drawing_panel.py b/lib/gui/simulator/drawing_panel.py index 0da58393e..aced15874 100644 --- a/lib/gui/simulator/drawing_panel.py +++ b/lib/gui/simulator/drawing_panel.py @@ -230,12 +230,27 @@ class DrawingPanel(wx.Panel): self.minx, self.miny, self.maxx, self.maxy = stitch_plan.bounding_box self.width = self.maxx - self.minx self.height = self.maxy - self.miny + self.dimensions_mm = stitch_plan.dimensions_mm self.num_stitches = stitch_plan.num_stitches + self.num_trims = stitch_plan.num_trims + self.num_color_changes = stitch_plan.num_color_blocks - 1 + self.num_stops = stitch_plan.num_stops + self.num_jumps = stitch_plan.num_jumps - 1 self.parse_stitch_plan(stitch_plan) self.choose_zoom_and_pan() self.set_current_stitch(0) + statusbar = self.GetTopLevelParent().statusbar + statusbar.SetStatusText( + _("Dimensions: {:.2f} x {:.2f}").format( + stitch_plan.dimensions_mm[0], + stitch_plan.dimensions_mm[1] + ), + 1 + ) self.loaded = True self.go() + if hasattr(self.view_panel, 'info_panel'): + self.view_panel.info_panel.update() def choose_zoom_and_pan(self, event=None): # ignore if EVT_SIZE fired before we load the stitch plan @@ -268,7 +283,8 @@ class DrawingPanel(wx.Panel): def color_to_pen(self, color): line_width = global_settings['simulator_line_width'] * PIXELS_PER_MM * self.PIXEL_DENSITY - return wx.Pen(list(map(int, color.visible_on_white.rgb)), int(line_width)) + background_color = self.GetBackgroundColour().GetAsString() + return wx.Pen(list(map(int, color.visible_on_background(background_color).rgb)), int(line_width)) def update_pen_size(self): line_width = global_settings['simulator_line_width'] * PIXELS_PER_MM * self.PIXEL_DENSITY @@ -339,7 +355,7 @@ class DrawingPanel(wx.Panel): command = self.commands[self.current_stitch] self.control_panel.on_current_stitch(self.current_stitch, command) statusbar = self.GetTopLevelParent().statusbar - statusbar.SetStatusText(_("Command: %s") % COMMAND_NAMES[command], 1) + statusbar.SetStatusText(_("Command: %s") % COMMAND_NAMES[command], 2) self.stop_if_at_end() self.Refresh() diff --git a/lib/gui/simulator/simulator_preferences.py b/lib/gui/simulator/simulator_preferences.py index a3e23bdcd..9954c8f2a 100644 --- a/lib/gui/simulator/simulator_preferences.py +++ b/lib/gui/simulator/simulator_preferences.py @@ -1,6 +1,6 @@ # Authors: see git history # -# Copyright (c) 2010 Authors +# Copyright (c) 2024 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. import wx @@ -54,7 +54,7 @@ class SimulatorPreferenceDialog(wx.Dialog): def on_change(self, attribute, event): global_settings[attribute] = event.EventObject.GetValue() - if attribute == 'simulator_line_width': + if self.drawing_panel.loaded and attribute == 'simulator_line_width': self.drawing_panel.update_pen_size() self.drawing_panel.Refresh() @@ -66,6 +66,7 @@ class SimulatorPreferenceDialog(wx.Dialog): def on_cancel(self, event): global_settings['simulator_line_width'] = self.line_width_value global_settings['simulator_npp_size'] = self.npp_size_value - self.drawing_panel.update_pen_size() - self.drawing_panel.Refresh() + if self.drawing_panel.loaded: + self.drawing_panel.update_pen_size() + self.drawing_panel.Refresh() self.Destroy() diff --git a/lib/gui/simulator/simulator_window.py b/lib/gui/simulator/simulator_window.py index 83321745d..2318041b6 100644 --- a/lib/gui/simulator/simulator_window.py +++ b/lib/gui/simulator/simulator_window.py @@ -17,8 +17,8 @@ class SimulatorWindow(wx.Frame): self.sizer = wx.BoxSizer(wx.VERTICAL) - self.statusbar = self.CreateStatusBar(2) - self.statusbar.SetStatusWidths((0, -1)) + self.statusbar = self.CreateStatusBar(3) + self.statusbar.SetStatusWidths((0, -1, -1)) if panel and parent: self.is_child = True diff --git a/lib/gui/simulator/split_simulator_window.py b/lib/gui/simulator/split_simulator_window.py index ce21a737f..72fd11433 100644 --- a/lib/gui/simulator/split_simulator_window.py +++ b/lib/gui/simulator/split_simulator_window.py @@ -18,7 +18,7 @@ class SplitSimulatorWindow(wx.Frame): self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE) - self.statusbar = self.CreateStatusBar(2) + self.statusbar = self.CreateStatusBar(3) self.detached_simulator_frame = None self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) @@ -56,13 +56,13 @@ class SplitSimulatorWindow(wx.Frame): self.detach_simulator() def splitter_resize(self, event): - self.statusbar.SetStatusWidths((self.simulator_panel.GetScreenPosition()[0], -1)) + self.statusbar.SetStatusWidths((self.simulator_panel.GetScreenPosition()[0], -1, -1)) def set_sash_position(self): settings_panel_min_size = self.settings_panel.GetSizer().CalcMin() debug.log(f"{settings_panel_min_size=}") self.splitter.SetSashPosition(settings_panel_min_size.width) - self.statusbar.SetStatusWidths((settings_panel_min_size.width, -1)) + self.statusbar.SetStatusWidths((settings_panel_min_size.width, -1, -1)) def cancel(self, event=None): if self.cancel_hook: @@ -86,7 +86,7 @@ class SplitSimulatorWindow(wx.Frame): self.simulator_panel.Reparent(self.splitter) self.splitter.SplitVertically(self.settings_panel, self.simulator_panel) - self.GetStatusBar().SetStatusText(self.detached_simulator_frame.GetStatusBar().GetStatusText(1), 1) + self.GetStatusBar().SetStatusText(self.detached_simulator_frame.GetStatusBar().GetStatusText(1), 2) self.detached_simulator_frame.Destroy() self.detached_simulator_frame = None @@ -114,7 +114,7 @@ class SplitSimulatorWindow(wx.Frame): self.detached_simulator_frame.SetSize((screen_rect.width - settings_panel_size.width, screen_rect.height)) self.detached_simulator_frame.SetPosition((settings_panel_size.width, screen_rect.top)) - self.detached_simulator_frame.GetStatusBar().SetStatusText(self.GetStatusBar().GetStatusText(1), 1) + self.detached_simulator_frame.GetStatusBar().SetStatusText(self.GetStatusBar().GetStatusText(1), 2) self.GetStatusBar().SetStatusText("", 1) self.detached_simulator_frame.Show() diff --git a/lib/gui/simulator/view_panel.py b/lib/gui/simulator/view_panel.py index cdcd52ee5..90d4fe9c0 100644 --- a/lib/gui/simulator/view_panel.py +++ b/lib/gui/simulator/view_panel.py @@ -8,6 +8,7 @@ from wx.lib.scrolledpanel import ScrolledPanel from ...debug.debug import debug from ...i18n import _ from . import SimulatorPreferenceDialog +from . import DesignInfoDialog class ViewPanel(ScrolledPanel): @@ -46,6 +47,11 @@ class ViewPanel(ScrolledPanel): self.btnColorChange.SetBitmap(self.control_panel.load_icon('color_change')) self.btnColorChange.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.on_marker_button('color_change', event)) + self.btnInfo = wx.BitmapButton(self, -1, style=self.button_style) + self.btnInfo.SetToolTip(_('Open info dialog')) + self.btnInfo.SetBitmap(self.control_panel.load_icon('info')) + self.btnInfo.Bind(wx.EVT_BUTTON, self.on_info_button) + self.btnBackgroundColor = wx.ColourPickerCtrl(self, -1, colour='white', size=((40, -1))) self.btnBackgroundColor.SetToolTip(_("Change background color")) self.btnBackgroundColor.Bind(wx.EVT_COLOURPICKER_CHANGED, self.on_update_background_color) @@ -73,7 +79,16 @@ class ViewPanel(ScrolledPanel): show_sizer.Add(0, 2, 0) show_sizer.Add(show_inner_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) show_sizer.Add(0, 2, 0) - outer_sizer.Add(show_sizer) + outer_sizer.Add(show_sizer, 0, wx.EXPAND) + outer_sizer.Add(0, 10, 0) + + info_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Info")), wx.VERTICAL) + info_inner_sizer = wx.BoxSizer(wx.VERTICAL) + info_inner_sizer.Add(self.btnInfo, 0, wx.EXPAND | wx.ALL, 2) + info_sizer.Add(0, 2, 0) + info_sizer.Add(info_inner_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) + info_sizer.Add(0, 2, 0) + outer_sizer.Add(info_sizer, 0, wx.EXPAND) outer_sizer.Add(0, 10, 0) settings_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Settings")), wx.VERTICAL) @@ -85,7 +100,7 @@ class ViewPanel(ScrolledPanel): settings_sizer.Add(0, 2, 0) settings_sizer.Add(settings_inner_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2) settings_sizer.Add(0, 2, 0) - outer_sizer.Add(settings_sizer) + outer_sizer.Add(settings_sizer, 0, wx.EXPAND) self.SetSizerAndFit(outer_sizer) @@ -113,5 +128,9 @@ class ViewPanel(ScrolledPanel): self.drawing_panel.Refresh() def on_settings_button(self, event): - simulator_panel = SimulatorPreferenceDialog(self, title=_('Simulator Preferences')) - simulator_panel.Show() + settings_panel = SimulatorPreferenceDialog(self, title=_('Simulator Preferences')) + settings_panel.Show() + + def on_info_button(self, event): + self.info_panel = DesignInfoDialog(self, title=_('Design Info')) + self.info_panel.Show() diff --git a/lib/threads/color.py b/lib/threads/color.py index 8b73c4f33..588610177 100644 --- a/lib/threads/color.py +++ b/lib/threads/color.py @@ -138,6 +138,34 @@ class ThreadColor(object): return ThreadColor(color, name=self.name, number=self.number, manufacturer=self.manufacturer, description=self.description, chart=self.chart) + def visible_on_background(self, background_color): + """A ThreadColor similar to this one but visible on given background color. + + Choose a color that's as close as possible to the actual thread color but is still at least + somewhat visible on given background. + """ + hls = list(colorsys.rgb_to_hls(*self.rgb_normalized)) + background = ThreadColor(background_color) + background_hls = list(colorsys.rgb_to_hls(*background.rgb_normalized)) + + difference = hls[1] - background_hls[1] + + if abs(difference) < 0.1: + if hls[1] > 0.5: + hls[1] -= 0.1 + else: + hls[1] += 0.1 + + color = colorsys.hls_to_rgb(*hls) + + # convert back to values in the range of 0-255 + color = tuple(value * 255 for value in color) + + return ThreadColor(color, name=self.name, number=self.number, manufacturer=self.manufacturer, + description=self.description, chart=self.chart) + + return self + @property def darker(self): hls = list(colorsys.rgb_to_hls(*self.rgb_normalized))