kopia lustrzana https://github.com/inkstitch/inkstitch
345 wiersze
14 KiB
Python
345 wiersze
14 KiB
Python
# 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 os
|
|
from sys import platform
|
|
|
|
import wx
|
|
from wx.lib.intctrl import IntCtrl
|
|
|
|
from ...debug.debug import debug
|
|
from ...i18n import _
|
|
from ...utils import get_resource_dir
|
|
from . import SimulatorSlider
|
|
|
|
|
|
class ControlPanel(wx.Panel):
|
|
""""""
|
|
|
|
@debug.time
|
|
def __init__(self, parent, *args, **kwargs):
|
|
""""""
|
|
self.parent = parent
|
|
self.stitch_plan = kwargs.pop('stitch_plan', None)
|
|
self.detach_callback = kwargs.pop('detach_callback', None)
|
|
self.target_stitches_per_second = kwargs.pop('stitches_per_second')
|
|
self.target_duration = kwargs.pop('target_duration')
|
|
kwargs['style'] = wx.BORDER_SUNKEN
|
|
wx.Panel.__init__(self, parent, *args, **kwargs)
|
|
|
|
self.drawing_panel = None
|
|
self.num_stitches = 0
|
|
self.current_stitch = 0
|
|
self.speed = 1
|
|
self.direction = 1
|
|
self._last_color_block_end = 0
|
|
|
|
self.icons_dir = get_resource_dir("icons")
|
|
|
|
# Widgets
|
|
self.button_size = self.GetTextExtent("M").y * 2
|
|
self.button_style = wx.BU_EXACTFIT | wx.BU_NOTEXT
|
|
self.btnMinus = wx.Button(self, -1, style=self.button_style)
|
|
self.btnMinus.Bind(wx.EVT_BUTTON, self.animation_slow_down)
|
|
self.btnMinus.SetBitmap(self.load_icon('slower'))
|
|
self.btnMinus.SetToolTip(_('Slow down (arrow down)'))
|
|
self.btnPlus = wx.Button(self, -1, style=self.button_style)
|
|
self.btnPlus.Bind(wx.EVT_BUTTON, self.animation_speed_up)
|
|
self.btnPlus.SetBitmap(self.load_icon('faster'))
|
|
self.btnPlus.SetToolTip(_('Speed up (arrow up)'))
|
|
self.btnBackwardStitch = wx.Button(self, -1, style=self.button_style)
|
|
self.btnBackwardStitch.Bind(wx.EVT_BUTTON, self.animation_one_stitch_backward)
|
|
self.btnBackwardStitch.SetBitmap(self.load_icon('backward_stitch'))
|
|
self.btnBackwardStitch.SetToolTip(_('Go backward one stitch (-)'))
|
|
self.btnForwardStitch = wx.Button(self, -1, style=self.button_style)
|
|
self.btnForwardStitch.Bind(wx.EVT_BUTTON, self.animation_one_stitch_forward)
|
|
self.btnForwardStitch.SetBitmap(self.load_icon('forward_stitch'))
|
|
self.btnForwardStitch.SetToolTip(_('Go forward one stitch (+)'))
|
|
self.btnBackwardCommand = wx.Button(self, -1, style=self.button_style)
|
|
self.btnBackwardCommand.Bind(wx.EVT_BUTTON, self.animation_one_command_backward)
|
|
self.btnBackwardCommand.SetBitmap(self.load_icon('backward_command'))
|
|
self.btnBackwardCommand.SetToolTip(_('Go backward one command (page-down)'))
|
|
self.btnForwardCommand = wx.Button(self, -1, style=self.button_style)
|
|
self.btnForwardCommand.Bind(wx.EVT_BUTTON, self.animation_one_command_forward)
|
|
self.btnForwardCommand.SetBitmap(self.load_icon('forward_command'))
|
|
self.btnForwardCommand.SetToolTip(_('Go forward one command (page-up)'))
|
|
self.btnDirection = wx.Button(self, -1, style=self.button_style)
|
|
self.btnDirection.Bind(wx.EVT_BUTTON, self.on_direction_button)
|
|
self.btnDirection.SetBitmap(self.load_icon('direction'))
|
|
self.btnDirection.SetToolTip(_('Switch animation direction (arrow left, arrow right)'))
|
|
self.btnPlay = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
|
self.btnPlay.Bind(wx.EVT_TOGGLEBUTTON, self.on_play_button)
|
|
self.btnPlay.SetBitmap(self.load_icon('play'))
|
|
self.btnPlay.SetToolTip(_('Play (P)'))
|
|
self.btnRestart = wx.Button(self, -1, style=self.button_style)
|
|
self.btnRestart.Bind(wx.EVT_BUTTON, self.animation_restart)
|
|
self.btnRestart.SetBitmap(self.load_icon('restart'))
|
|
self.btnRestart.SetToolTip(_('Restart (R)'))
|
|
self.slider = SimulatorSlider(self, -1, value=1, minValue=1, maxValue=2)
|
|
self.slider.Bind(wx.EVT_SLIDER, self.on_slider)
|
|
self.stitchBox = IntCtrl(self, -1, value=1, min=1, max=2, limited=True, allow_none=True,
|
|
size=((100, -1)), style=wx.TE_PROCESS_ENTER)
|
|
self.stitchBox.Clear()
|
|
self.stitchBox.Bind(wx.EVT_LEFT_DOWN, self.on_stitch_box_focus)
|
|
self.stitchBox.Bind(wx.EVT_SET_FOCUS, self.on_stitch_box_focus)
|
|
self.stitchBox.Bind(wx.EVT_TEXT_ENTER, self.on_stitch_box_focusout)
|
|
self.stitchBox.Bind(wx.EVT_KILL_FOCUS, self.on_stitch_box_focusout)
|
|
self.Bind(wx.EVT_LEFT_DOWN, self.on_stitch_box_focusout)
|
|
self.totalstitchText = wx.StaticText(self, -1, label="")
|
|
extent = self.totalstitchText.GetTextExtent("0000000")
|
|
self.totalstitchText.SetMinSize(extent)
|
|
|
|
# Layout
|
|
self.hbSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
|
self.hbSizer1.Add(self.slider, 1, wx.EXPAND | wx.RIGHT, 10)
|
|
self.hbSizer1.Add(self.stitchBox, 0, wx.ALIGN_TOP | wx.TOP, 25)
|
|
self.hbSizer1.Add((1, 1), 0, wx.RIGHT, 10)
|
|
self.hbSizer1.Add(self.totalstitchText, 0, wx.ALIGN_TOP | wx.TOP, 25)
|
|
self.hbSizer1.Add((1, 1), 0, wx.RIGHT, 10)
|
|
|
|
self.controls_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Controls")), wx.HORIZONTAL)
|
|
self.controls_inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
self.controls_inner_sizer.Add(self.btnBackwardCommand, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_inner_sizer.Add(self.btnBackwardStitch, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_inner_sizer.Add(self.btnForwardStitch, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_inner_sizer.Add(self.btnForwardCommand, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_inner_sizer.Add(self.btnDirection, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_inner_sizer.Add(self.btnPlay, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_inner_sizer.Add(self.btnRestart, 0, wx.EXPAND | wx.ALL, 2)
|
|
self.controls_sizer.Add((1, 1), 1)
|
|
self.controls_sizer.Add(self.controls_inner_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
|
|
self.controls_sizer.Add((1, 1), 1)
|
|
|
|
self.speed_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Speed")), wx.VERTICAL)
|
|
|
|
self.speed_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
self.speed_buttons_sizer.Add((1, 1), 1)
|
|
self.speed_buttons_sizer.Add(self.btnMinus, 0, wx.ALL, 2)
|
|
self.speed_buttons_sizer.Add(self.btnPlus, 0, wx.ALL, 2)
|
|
self.speed_buttons_sizer.Add((1, 1), 1)
|
|
self.speed_sizer.Add(self.speed_buttons_sizer, 0, wx.EXPAND | wx.ALL)
|
|
self.speed_text = wx.StaticText(self, wx.ID_ANY, label="", style=wx.ALIGN_CENTRE_HORIZONTAL | wx.ST_NO_AUTORESIZE)
|
|
self.speed_text.SetFont(wx.Font(wx.FontInfo(10).Bold()))
|
|
extent = self.speed_text.GetTextExtent(self.format_speed_text(100000))
|
|
self.speed_text.SetMinSize(extent)
|
|
self.speed_sizer.Add(self.speed_text, 0, wx.EXPAND | wx.ALL, 5)
|
|
|
|
# A normal BoxSizer can only make child components the same or
|
|
# proportional size. A FlexGridSizer can split up the available extra
|
|
# space evenly among all growable columns.
|
|
self.control_row2_sizer = wx.FlexGridSizer(cols=3, vgap=0, hgap=5)
|
|
self.control_row2_sizer.AddGrowableCol(0)
|
|
self.control_row2_sizer.AddGrowableCol(1)
|
|
self.control_row2_sizer.AddGrowableCol(2)
|
|
self.control_row2_sizer.Add(self.controls_sizer, 0, wx.EXPAND)
|
|
self.control_row2_sizer.Add(self.speed_sizer, 0, wx.EXPAND)
|
|
|
|
self.vbSizer = vbSizer = wx.BoxSizer(wx.VERTICAL)
|
|
vbSizer.Add(self.hbSizer1, 1, wx.EXPAND | wx.ALL, 10)
|
|
vbSizer.Add(self.control_row2_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
|
|
self.SetSizerAndFit(vbSizer)
|
|
|
|
# wait for layouts so that panel size is set
|
|
if self.stitch_plan:
|
|
wx.CallLater(50, self.load, self.stitch_plan)
|
|
|
|
def set_drawing_panel(self, drawing_panel):
|
|
self.drawing_panel = drawing_panel
|
|
self.drawing_panel.set_speed(self.speed)
|
|
|
|
def _set_num_stitches(self, num_stitches):
|
|
if num_stitches < 2:
|
|
# otherwise the slider and intctrl get mad
|
|
num_stitches = 2
|
|
self.num_stitches = num_stitches
|
|
self.stitchBox.SetValue(1)
|
|
self.stitchBox.SetMax(num_stitches)
|
|
self.slider.SetMax(num_stitches)
|
|
self.totalstitchText.SetLabel(f"/ { num_stitches }")
|
|
self.choose_speed()
|
|
|
|
def clear(self):
|
|
self.stitches = []
|
|
self._set_num_stitches(0)
|
|
self.slider.clear()
|
|
self.stitchBox.Clear()
|
|
self.totalstitchText.SetLabel("")
|
|
|
|
def load(self, stitch_plan):
|
|
self.clear()
|
|
self.stitches = []
|
|
self._set_num_stitches(stitch_plan.num_stitches)
|
|
|
|
stitch_num = 0
|
|
last_block_end = 1
|
|
for color_block in stitch_plan.color_blocks:
|
|
self.stitches.extend(color_block.stitches)
|
|
|
|
start = stitch_num + 1
|
|
end = start + color_block.num_stitches - 1
|
|
self.slider.add_color_section(color_block.color.rgb, last_block_end, end)
|
|
last_block_end = end
|
|
|
|
for stitch_num, stitch in enumerate(color_block.stitches, start):
|
|
if stitch.trim:
|
|
self.slider.add_marker("trim", stitch_num)
|
|
elif stitch.stop:
|
|
self.slider.add_marker("stop", stitch_num)
|
|
elif stitch.jump:
|
|
self.slider.add_marker("jump", stitch_num)
|
|
elif stitch.color_change:
|
|
self.slider.add_marker("color_change", stitch_num)
|
|
|
|
def is_dark_theme(self):
|
|
return wx.SystemSettings().GetAppearance().IsDark()
|
|
|
|
def load_icon(self, icon_name):
|
|
if self.is_dark_theme() and platform != "win32":
|
|
icon = wx.Image(os.path.join(self.icons_dir, f"{icon_name}_dark.png"))
|
|
else:
|
|
icon = wx.Image(os.path.join(self.icons_dir, f"{icon_name}.png"))
|
|
icon.Rescale(self.button_size, self.button_size, wx.IMAGE_QUALITY_HIGH)
|
|
return icon.ConvertToBitmap()
|
|
|
|
def choose_speed(self):
|
|
if self.target_duration:
|
|
self.set_speed(int(self.num_stitches / float(self.target_duration)))
|
|
else:
|
|
self.set_speed(self.target_stitches_per_second)
|
|
|
|
def animation_forward(self, event=None):
|
|
self.drawing_panel.forward()
|
|
self.direction = 1
|
|
self.update_speed_text()
|
|
|
|
def animation_reverse(self, event=None):
|
|
self.drawing_panel.reverse()
|
|
self.direction = -1
|
|
self.update_speed_text()
|
|
|
|
def on_direction_button(self, event):
|
|
if self.direction == -1:
|
|
self.animation_forward()
|
|
else:
|
|
self.animation_reverse()
|
|
|
|
def set_speed(self, speed):
|
|
self.speed = int(max(speed, 1))
|
|
self.update_speed_text()
|
|
|
|
if self.drawing_panel:
|
|
self.drawing_panel.set_speed(self.speed)
|
|
|
|
def format_speed_text(self, speed):
|
|
return _('%d stitches/sec') % speed
|
|
|
|
def update_speed_text(self):
|
|
self.speed_text.SetLabel(self.format_speed_text(self.speed * self.direction))
|
|
|
|
def on_slider(self, event):
|
|
self.animation_pause()
|
|
stitch = event.GetEventObject().GetValue()
|
|
self.stitchBox.SetValue(stitch)
|
|
|
|
if self.drawing_panel:
|
|
self.drawing_panel.set_current_stitch(stitch)
|
|
|
|
self.parent.SetFocus()
|
|
|
|
def on_current_stitch(self, stitch, command):
|
|
if self.current_stitch != stitch:
|
|
self.current_stitch = stitch
|
|
self.slider.SetValue(stitch)
|
|
self.stitchBox.SetValue(stitch)
|
|
|
|
def on_stitch_box_focus(self, event):
|
|
self.animation_pause()
|
|
self.parent.SetAcceleratorTable(wx.AcceleratorTable([]))
|
|
event.Skip()
|
|
|
|
def on_stitch_box_focusout(self, event):
|
|
self.parent.SetAcceleratorTable(self.parent.accel_table)
|
|
stitch = self.stitchBox.GetValue()
|
|
# We now want to remove the focus from the stitchBox.
|
|
# In Windows it won't work if we set focus to self.parent, while setting the focus to the
|
|
# top level would work. This in turn would activate the trim button in Linux. So let's
|
|
# set the focus on the slider instead where it doesn't cause any harm in any of the operating systems
|
|
self.slider.SetFocus()
|
|
|
|
if stitch is None:
|
|
stitch = 1
|
|
self.stitchBox.SetValue(1)
|
|
|
|
self.slider.SetValue(stitch)
|
|
|
|
if self.drawing_panel:
|
|
self.drawing_panel.set_current_stitch(stitch)
|
|
event.Skip()
|
|
|
|
def animation_slow_down(self, event):
|
|
""""""
|
|
self.set_speed(self.speed / 2.0)
|
|
|
|
def animation_speed_up(self, event):
|
|
""""""
|
|
self.set_speed(self.speed * 2.0)
|
|
|
|
def animation_pause(self, event=None):
|
|
self.drawing_panel.stop()
|
|
|
|
def animation_start(self, event=None):
|
|
self.drawing_panel.go()
|
|
|
|
def on_start(self):
|
|
self.btnPlay.SetValue(True)
|
|
|
|
def on_stop(self):
|
|
self.btnPlay.SetValue(False)
|
|
|
|
def on_play_button(self, event):
|
|
play = self.btnPlay.GetValue()
|
|
if play:
|
|
self.animation_start()
|
|
else:
|
|
self.animation_pause()
|
|
|
|
def play_or_pause(self, event):
|
|
if self.drawing_panel.animating:
|
|
self.animation_pause()
|
|
else:
|
|
self.animation_start()
|
|
|
|
def animation_one_stitch_forward(self, event):
|
|
self.animation_pause()
|
|
self.drawing_panel.one_stitch_forward()
|
|
|
|
def animation_one_stitch_backward(self, event):
|
|
self.animation_pause()
|
|
self.drawing_panel.one_stitch_backward()
|
|
|
|
def animation_one_command_backward(self, event):
|
|
self.animation_pause()
|
|
stitch_number = self.current_stitch - 1
|
|
while stitch_number >= 1:
|
|
# stitch number shown to the user starts at 1
|
|
stitch = self.stitches[stitch_number - 1]
|
|
if stitch.jump or stitch.trim or stitch.stop or stitch.color_change:
|
|
break
|
|
stitch_number -= 1
|
|
self.drawing_panel.set_current_stitch(stitch_number)
|
|
|
|
def animation_one_command_forward(self, event):
|
|
self.animation_pause()
|
|
stitch_number = self.current_stitch + 1
|
|
while stitch_number <= self.num_stitches:
|
|
# stitch number shown to the user starts at 1
|
|
stitch = self.stitches[stitch_number - 1]
|
|
if stitch.jump or stitch.trim or stitch.stop or stitch.color_change:
|
|
break
|
|
stitch_number += 1
|
|
self.drawing_panel.set_current_stitch(stitch_number)
|
|
|
|
def animation_restart(self, event):
|
|
self.drawing_panel.restart()
|