From 33e5645736cea4e1e8cc6d3511caf30d76398a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Thu, 12 Sep 2019 12:49:53 +0200 Subject: [PATCH] Initial implementation of assets window & external object tool. libgui/GuiFrame.py:GuiMiniFrame(): added as sub-class of wx.MiniFrame(). libgui/GuiWindow.py:RoarCanvasWindowDropTarget(): initial implementation. libgui/GuiWindow.py:__init__(): receive & pass optional style parameter. libgui/GuiWindow.py:__init__(): set drop target. libroar/RoarAssetsWindow.py: initial implementation. libroar/RoarClient.py:__init__(): add RoarAssetsWindow(). libtools/ToolObject.py: initial implementation. assets/text/TODO: updated. --- assets/text/TODO | 16 +- libgui/GuiFrame.py | 6 + libgui/GuiWindow.py | 6 +- libroar/RoarAssetsWindow.py | 326 +++++++++++++++++++++++++++++ libroar/RoarCanvasCommandsEdit.py | 17 +- libroar/RoarCanvasCommandsTools.py | 3 +- libroar/RoarCanvasWindow.py | 32 +++ libroar/RoarClient.py | 3 + libtools/ToolObject.py | 126 +++++++++++ 9 files changed, 522 insertions(+), 13 deletions(-) create mode 100644 libroar/RoarAssetsWindow.py create mode 100644 libtools/ToolObject.py diff --git a/assets/text/TODO b/assets/text/TODO index 2c275ab..f67bf8f 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -1,11 +1,11 @@ 1) Implement ANSI CSI CU[BDPU] sequences & italic -2) Implement instrumentation & unit tests, document -3) Open and toggle a reference image in the background -4) Client-Server or Peer-to-Peer realtime collaboration -5) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.) -6) Hotkey & graphical interfaces to {composed,parametrised} tools -7) Incremental auto{load,save} & {backup,restore} (needs Settings window) -8) Layers, layout (e.g. for comics, zines, etc.) & asset management (e.g. kade, lion, etc.) & traits w/ {inserting,merging,linking} +2) Layers & layout (e.g. for comics, zines, etc.) +3) Implement instrumentation & unit tests, document +4) Open and toggle a reference image in the background +5) Client-Server or Peer-to-Peer realtime collaboration +6) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.) +7) Hotkey & graphical interfaces to {composed,parametrised} tools +8) Incremental auto{load,save} & {backup,restore} (needs Settings window) 9) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...) 10) Composition and parametrisation of tools from higher-order operators (brushes, filters, outlines, patterns & shaders) and unit tools; unit tools: a) geometric primitives (arrow, circle, cloud/speech bubble, curve, heart, hexagon, line, pentagon, polygon, rhombus, triangle, square, star) @@ -15,7 +15,7 @@ a) {copy,cut,insert from,paste}, {de,in}crease cell size b) replace logo w/ canvas panel in About dialogue c) switch from wxPython to GTK - d) fix underscore rendering + d) edit asset in new canvas e) MRU {directories,files} f) ruler 12) fix outstanding {re,un}do bugs diff --git a/libgui/GuiFrame.py b/libgui/GuiFrame.py index 47d4759..63fb1bb 100644 --- a/libgui/GuiFrame.py +++ b/libgui/GuiFrame.py @@ -175,4 +175,10 @@ class GuiFrame(wx.Frame): for event, f in ((wx.EVT_CHAR, self.onChar), (wx.EVT_MENU, self.onMenu), (wx.EVT_MOUSEWHEEL, self.onMouseWheel)): self.Bind(event, f) +class GuiMiniFrame(wx.MiniFrame): + # + # __init__(self, parent, size, title, pos=wx.DefaultPosition): initialisation method + def __init__(self, parent, size, title, pos=wx.DefaultPosition): + super().__init__(parent, id=wx.ID_ANY, pos=pos, size=size, title=title) + # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libgui/GuiWindow.py b/libgui/GuiWindow.py index 63794ee..ed476cb 100644 --- a/libgui/GuiWindow.py +++ b/libgui/GuiWindow.py @@ -55,9 +55,9 @@ class GuiWindow(wx.ScrolledWindow): # }}} # - # __init__(self, parent, pos, scrollStep, size): initialisation method - def __init__(self, parent, pos, scrollStep, size): - super().__init__(parent, pos=pos, size=size) + # __init__(self, parent, pos, scrollStep, size, style=0): initialisation method + def __init__(self, parent, pos, scrollStep, size, style=0): + super().__init__(parent, pos=pos, size=size, style=style) self.pos, self.scrollFlag, self.scrollStep, self.size = pos, False, scrollStep, size for eventType, f in ( (wx.EVT_CHAR, self.onKeyboardInput), (wx.EVT_CLOSE, self.onClose), (wx.EVT_LEAVE_WINDOW, self.onLeaveWindow), diff --git a/libroar/RoarAssetsWindow.py b/libroar/RoarAssetsWindow.py new file mode 100644 index 0000000..d5f15b3 --- /dev/null +++ b/libroar/RoarAssetsWindow.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +# +# RoarAssetsWindow.py +# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz +# + +from Canvas import Canvas +from GuiFrame import GuiMiniFrame +from GuiWindow import GuiWindow +import json, os, sys, wx + +class RoarAssetsWindow(GuiMiniFrame): + # {{{ _drawPatch(self, eventDc, isCursor, patch, viewRect) + def _drawPatch(self, eventDc, isCursor, patch, viewRect): + if not isCursor: + self.backend.drawPatch(eventDc, patch, viewRect) + # }}} + # {{{ _import(self, f, pathName) + def _import(self, f, pathName): + rc = False + self.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) + try: + canvas = Canvas((0, 0)) + rc, error, newMap, newPathName, newSize = f(canvas, pathName) + if rc: + self.update(canvas, newSize, newMap) + except FileNotFoundError as e: + rc, error, newMap, newPathName, newSize = False, str(e), None, None, None + self.SetCursor(wx.Cursor(wx.NullCursor)) + return rc, error, canvas, newMap, newPathName, newSize + # }}} + # {{{ _importFiles(self, f, wildcard) + def _importFiles(self, f, wildcard): + resultList = [] + with wx.FileDialog(self, "Load...", os.getcwd(), "", wildcard, wx.FD_MULTIPLE | wx.FD_OPEN) as dialog: + if dialog.ShowModal() == wx.ID_CANCEL: + resultList += [[False, "(cancelled)", None, None, None, None]] + else: + for pathName in dialog.GetPaths(): + resultList += [self._import(f, pathName)] + return resultList + # }}} + # {{{ _updateScrollBars(self) + def _updateScrollBars(self): + clientSize = self.panelCanvas.GetClientSize() + if (self.panelCanvas.size[0] > clientSize[0]) or (self.panelCanvas.size[1] > clientSize[1]): + self.scrollFlag = True; super(wx.ScrolledWindow, self.panelCanvas).SetVirtualSize(self.panelCanvas.size); + elif self.scrollFlag \ + and ((self.panelCanvas.size[0] <= clientSize[0]) or (self.panelCanvas.size[1] <= clientSize[1])): + self.scrollFlag = False; super(wx.ScrolledWindow, self.panelCanvas).SetVirtualSize((0, 0)); + # }}} + + # {{{ drawCanvas(self, canvas) + def drawCanvas(self, canvas): + panelSize = [a * b for a, b in zip(canvas.size, self.cellSize)] + self.panelCanvas.SetMinSize(panelSize); self.panelCanvas.SetSize(wx.DefaultCoord, wx.DefaultCoord, *panelSize); + curWindow = self.panelCanvas + while curWindow != None: + curWindow.Layout(); curWindow = curWindow.GetParent(); + self.backend.resize(canvas.size, self.cellSize) + viewRect = self.panelCanvas.GetViewStart(); + eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect) + for numRow in range(canvas.size[1]): + for numCol in range(canvas.size[0]): + self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect) + # }}} + # {{{ onPaint(self, event) + def onPaint(self, event): + self.backend.onPaint(self.panelCanvas.GetClientSize(), self.panelCanvas, self.panelCanvas.GetViewStart()) + # }}} + # {{{ onPanelLeftDown(self, event) + def onPanelLeftDown(self, event): + self.panelCanvas.SetFocus() + if (self.currentIndex != None): + dataText = json.dumps((self.canvasList[self.currentIndex][0].map, self.canvasList[self.currentIndex][0].size,)) + textDataObject = wx.TextDataObject(dataText) + dropSource = wx.DropSource(event.GetEventObject()) + dropSource.SetData(textDataObject) + result = dropSource.DoDragDrop(True) + event.Skip() + # }}} + # {{{ onPanelPaint(self, event) + def onPanelPaint(self, event): + self.backend.onPaint(self.panelCanvas.GetClientSize(), self.panelCanvas, self.panelCanvas.GetViewStart()) + # }}} + # {{{ onPanelSize(self, event) + def onPanelSize(self, event): + self._updateScrollBars(); event.Skip(); + # }}} + # {{{ resize(self, canvas, newSize) + def resize(self, canvas, newSize): + oldSize = [0, 0] if canvas.map == None else canvas.size + deltaSize = [b - a for a, b in zip(oldSize, newSize)] + if canvas.resize(newSize, False): + panelSize = [a * b for a, b in zip(canvas.size, self.cellSize)] + self.panelCanvas.SetMinSize(panelSize); self.panelCanvas.SetSize(wx.DefaultCoord, wx.DefaultCoord, *panelSize); + curWindow = self.panelCanvas + while curWindow != None: + curWindow.Layout(); curWindow = curWindow.GetParent(); + self.backend.resize(newSize, self.cellSize) + viewRect = self.panelCanvas.GetViewStart(); eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect); + if deltaSize[0] > 0: + for numRow in range(oldSize[1]): + for numNewCol in range(oldSize[0], newSize[0]): + self._drawPatch(eventDc, False, [numNewCol, numRow, 1, 1, 0, " "], viewRect) + if deltaSize[1] > 1: + for numNewRow in range(oldSize[1], newSize[1]): + for numNewCol in range(newSize[0]): + self._drawPatch(eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "], viewRect) + # }}} + # {{{ update(self, canvas, newSize, newCanvas=None) + def update(self, canvas, newSize, newCanvas=None): + self.resize(canvas, newSize); + canvas.update(newSize, newCanvas); viewRect = self.panelCanvas.GetViewStart(); + viewRect = self.panelCanvas.GetViewStart(); + eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect) + for numRow in range(canvas.size[1]): + for numCol in range(canvas.size[0]): + self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect) + # }}} + + # {{{ onImportAnsi(self, event) + def onImportAnsi(self, event): + event.Skip() + # }}} + # {{{ onImportFromClipboard(self, event) + def onImportFromClipboard(self, event): + event.Skip() + # }}} + # {{{ onImportSauce(self, event) + def onImportSauce(self, event): + event.Skip() + # }}} + # {{{ onChar(self, event) + def onChar(self, event): + if (event.GetModifiers() == wx.MOD_NONE) \ + and (event.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP)): + self.listView.SetFocus() + return wx.PostEvent(self.listView, event) + else: + event.Skip() + # }}} + # {{{ onListViewChar(self, event) + def onListViewChar(self, event): + index, rc = self.listView.GetFirstSelected(), False + if index != -1: + keyChar, keyModifiers = event.GetKeyCode(), event.GetModifiers() + if (keyChar, keyModifiers) == (wx.WXK_DELETE, wx.MOD_NONE): + self.currentIndex, rc = index, True; self.onRemove(None); + if not rc: + event.Skip() + # }}} + # {{{ onListViewItemSelected(self, event) + def onListViewItemSelected(self, event): + self.currentIndex = event.GetItem().GetId() + item = [self.listView.GetItem(self.currentIndex, col).GetText() for col in (0, 1)] + self.drawCanvas(self.canvasList[self.currentIndex][0]) + # }}} + # {{{ onListViewRightDown(self, event) + def onListViewRightDown(self, event): + eventPoint = event.GetPosition() + if self.currentIndex == None: + index, flags = self.listView.HitTest(eventPoint) + if index != wx.NOT_FOUND: + self.currentIndex = index + if self.currentIndex == None: + self.contextMenuItems[4].Enable(False) + else: + self.contextMenuItems[4].Enable(True) + self.PopupMenu(self.contextMenu, eventPoint) + # }}} + # {{{ onLoad(self, event) + def onLoad(self, event): + def importmIRC(canvas, pathName): + rc, error = canvas.importStore.importTextFile(pathName) + return (rc, error, canvas.importStore.outMap, pathName, canvas.importStore.inSize) + for rc, error, canvas, newMap, newPathName, newSize in self._importFiles(importmIRC, "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*"): + if rc: + self.currentIndex = self.listView.GetItemCount() + self.canvasList[self.currentIndex] = [canvas, newPathName] + self.listView.InsertItem(self.currentIndex, "") + idx = -1 + while True: + idx = self.listView.GetNextSelected(idx) + if idx != -1: + self.listView.Select(idx, on=0) + else: + break + self.listView.Select(self.currentIndex, on=1) + self.listView.SetFocus() + [self.listView.SetItem(self.currentIndex, col, label) for col, label in zip((0, 1), (os.path.basename(newPathName), "{}x{}".format(*newSize)))] + [self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE) for col in (0, 1)] + else: + with wx.MessageDialog(self, "Error: {}".format(error), "", wx.CANCEL | wx.OK | wx.OK_DEFAULT) as dialog: + dialogChoice = dialog.ShowModal() + if dialogChoice == wx.ID_CANCEL: + break + # }}} + # {{{ onLoadList(self, event) + def onLoadList(self, event): + rc = True + with wx.FileDialog(self, "Load from list...", os.getcwd(), "", "List files (*.lst)|*.lst|Text files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog: + if dialog.ShowModal() != wx.ID_CANCEL: + try: + pathName = dialog.GetPath() + with open(pathName, "r") as fileObject: + try: + for line in fileObject.readlines(): + line = line.rstrip("\r\n") + if not os.path.isabs(line): + line = os.path.join(os.path.dirname(pathName), line) + def importmIRC(canvas, pathName): + rc, error = canvas.importStore.importTextFile(pathName) + return (rc, error, canvas.importStore.outMap, pathName, canvas.importStore.inSize) + rc, error, canvas, newMap, newPathName, newSize = self._import(importmIRC, line) + if rc: + self.currentIndex = self.listView.GetItemCount() + self.canvasList[self.currentIndex] = [canvas, newPathName] + self.listView.InsertItem(self.currentIndex, "") + idx = -1 + while True: + idx = self.listView.GetNextSelected(idx) + if idx != -1: + self.listView.Select(idx, on=0) + else: + break + self.listView.Select(self.currentIndex, on=1) + self.listView.SetFocus() + [self.listView.SetItem(self.currentIndex, col, label) for col, label in zip((0, 1), (os.path.basename(newPathName), "{}x{}".format(*newSize)))] + [self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE) for col in (0, 1)] + else: + with wx.MessageDialog(self, "Error: {}".format(error), "", wx.CANCEL | wx.OK | wx.OK_DEFAULT) as dialog: + dialogChoice = dialog.ShowModal() + if dialogChoice == wx.ID_CANCEL: + self.SetCursor(wx.Cursor(wx.NullCursor)); break; + except: + self.SetCursor(wx.Cursor(wx.NullCursor)) + with wx.MessageDialog(self, "Error: {}".format(str(sys.exc_info()[1])), "", wx.OK | wx.OK_DEFAULT) as dialog: + dialogChoice = dialog.ShowModal() + except FileNotFoundError as e: + self.SetCursor(wx.Cursor(wx.NullCursor)) + with wx.MessageDialog(self, "Error: {}".format(str(e)), "", wx.OK | wx.OK_DEFAULT) as dialog: + dialogChoice = dialog.ShowModal() + # }}} + # {{{ onRemove(self, event) + def onRemove(self, event): + del self.canvasList[self.currentIndex]; self.listView.DeleteItem(self.currentIndex); + itemCount = self.listView.GetItemCount() + if itemCount > 0: + for numCanvas in [n for n in sorted(self.canvasList.keys()) if n >= self.currentIndex]: + self.canvasList[numCanvas - 1] = self.canvasList[numCanvas]; del self.canvasList[numCanvas]; + [self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE) for col in (0, 1)] + if (self.currentIndex == 0) or (self.currentIndex >= itemCount): + self.currentIndex = 0 if itemCount > 0 else None + else: + self.currentIndex = self.currentIndex if self.currentIndex < itemCount else None + if self.currentIndex != None: + self.listView.Select(self.currentIndex, on=1) + self.drawCanvas(self.canvasList[self.currentIndex][0]) + else: + self.currentIndex = None + [self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE_USEHEADER) for col in (0, 1)] + self.drawCanvas(Canvas((0, 0))) + # }}} + # {{{ onSaveList(self, event) + def onSaveList(self, event): + rc = True + if len(self.canvasList): + with wx.FileDialog(self, "Save as list...", os.getcwd(), "", "List files (*.lst)|*.lst|Text files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_SAVE) as dialog: + if dialog.ShowModal() != wx.ID_CANCEL: + with open(dialog.GetPath(), "w") as fileObject: + for pathName in [self.canvasList[k][1] for k in self.canvasList.keys()]: + print(pathName, file=fileObject) + rc = True + else: + rc, error = False, "no assets currently loaded" + if not rc: + with wx.MessageDialog(self, "Error: {}".format(error), "", wx.OK | wx.OK_DEFAULT) as dialog: + dialogChoice = dialog.ShowModal() + # }}} + + # + # __init__(self, backend, cellSize, parent, pos=None, size=(400, 400), title="Assets"): initialisation method + def __init__(self, backend, cellSize, parent, pos=None, size=(400, 400), title="Assets"): + if pos == None: + parentRect = parent.GetScreenRect(); pos = (parentRect.x + parentRect.width, parentRect.y); + super().__init__(parent, size, title, pos=pos) + self.backend, self.canvasList = backend((0, 0), cellSize), {} + self.cellSize, self.currentIndex, self.leftDown, self.parent, self.scrollFlag = cellSize, None, False, parent, False + self.Bind(wx.EVT_CHAR, self.onChar) + + self.contextMenu, self.contextMenuItems = wx.Menu(), [] + for text, f in ( + ("&Load...", self.onLoad), + ("Import &ANSI...", self.onImportAnsi), + ("Import &SAUCE...", self.onImportSauce), + ("Import from &clipboard", self.onImportFromClipboard), + ("&Remove", self.onRemove), + (None, None), + ("Load from l&ist...", self.onLoadList), + ("Sa&ve as list...", self.onSaveList),): + if (text, f) == (None, None): + self.contextMenu.AppendSeparator() + else: + self.contextMenuItems += [wx.MenuItem(self.contextMenu, wx.NewId(), text)] + self.Bind(wx.EVT_MENU, f, self.contextMenuItems[-1]) + self.contextMenu.Append(self.contextMenuItems[-1]) + + self.listView = wx.ListView(self, -1, size=([int(m / n) for m, n in zip(size, (2, 4))]), style=wx.BORDER_SUNKEN | wx.LC_REPORT) + [self.listView.InsertColumn(col, heading) for col, heading in ((0, "Name"), (1, "Size"))] + self.listView.Bind(wx.EVT_CHAR, self.onListViewChar) + self.listView.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onListViewItemSelected) + self.listView.Bind(wx.EVT_RIGHT_DOWN, self.onListViewRightDown) + + self.panelCanvas = GuiWindow(self, (0, 0), cellSize, (int(size[0] / 2), int(size[1] / 2)), wx.BORDER_SUNKEN) + self.panelCanvas.Bind(wx.EVT_LEFT_DOWN, self.onPanelLeftDown) + self.panelCanvas.Bind(wx.EVT_PAINT, self.onPanelPaint) + self.panelCanvas.Bind(wx.EVT_SIZE, self.onPanelSize) + self._updateScrollBars() + + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.sizer.AddMany(((self.listView, 0, wx.ALL | wx.EXPAND, 4), (self.panelCanvas, 1, wx.ALL | wx.EXPAND, 4),)) + self.SetSizerAndFit(self.sizer) + self.Show(True) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libroar/RoarCanvasCommandsEdit.py b/libroar/RoarCanvasCommandsEdit.py index f9f79fb..09b23f3 100644 --- a/libroar/RoarCanvasCommandsEdit.py +++ b/libroar/RoarCanvasCommandsEdit.py @@ -8,6 +8,20 @@ from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDeco import wx class RoarCanvasCommandsEdit(): + # {{{ canvasAssetsWindowHide(self, event) + @GuiCommandDecorator("Hide assets window", "Hide assets window", None, None, False) + def canvasAssetsWindowHide(self, event): + self.parentFrame.assetsWindow.Show(False) + self.parentFrame.menuItemsById[self.canvasAssetsWindowHide.attrDict["id"]].Enable(False) + self.parentFrame.menuItemsById[self.canvasAssetsWindowShow.attrDict["id"]].Enable(True) + # }}} + # {{{ canvasAssetsWindowShow(self, event) + @GuiCommandDecorator("Show assets window", "Show assets window", None, None, False) + def canvasAssetsWindowShow(self, event): + self.parentFrame.assetsWindow.Show(True) + self.parentFrame.menuItemsById[self.canvasAssetsWindowHide.attrDict["id"]].Enable(True) + self.parentFrame.menuItemsById[self.canvasAssetsWindowShow.attrDict["id"]].Enable(False) + # }}} # {{{ canvasBrush(self, f, idx) @GuiSelectDecorator(0, "Solid brush", "Solid brush", None, None, True) def canvasBrush(self, f, idx): @@ -150,7 +164,8 @@ class RoarCanvasCommandsEdit(): self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False), NID_MENU_SEP, self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), NID_MENU_SEP, self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False), NID_MENU_SEP, - self.canvasBrush(self.canvasBrush, 0), + self.canvasBrush(self.canvasBrush, 0), NID_MENU_SEP, + self.canvasAssetsWindowHide, self.canvasAssetsWindowShow, ), ) self.toolBars = () diff --git a/libroar/RoarCanvasCommandsTools.py b/libroar/RoarCanvasCommandsTools.py index 3f2308c..2d9fa23 100644 --- a/libroar/RoarCanvasCommandsTools.py +++ b/libroar/RoarCanvasCommandsTools.py @@ -25,7 +25,7 @@ class RoarCanvasCommandsTools(): @GuiSelectDecorator(6, "Text", "&Text", ["toolText.png"], [wx.ACCEL_CTRL, ord("T")], False) def canvasTool(self, f, idx): def canvasTool_(event): - self.currentTool = [ToolCircle, ToolSelectClone, ToolFill, ToolLine, ToolSelectMove, ToolRect, ToolText][idx]() + self.lastTool, self.currentTool = self.currentTool, [ToolCircle, ToolSelectClone, ToolFill, ToolLine, ToolSelectMove, ToolRect, ToolText][idx]() self.parentFrame.menuItemsById[self.canvasTool.attrList[idx]["id"]].Check(True) toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[idx]["id"]].GetToolBar() toolBar.ToggleTool(self.canvasTool.attrList[idx]["id"], True) @@ -47,5 +47,6 @@ class RoarCanvasCommandsTools(): ), ) self.toolBars = () + self.currentTool, self.lastTool = None, None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index 9962201..de96c1b 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -5,6 +5,33 @@ # from GuiWindow import GuiWindow +from ToolObject import ToolObject +import json, wx, sys + +class RoarCanvasWindowDropTarget(wx.TextDropTarget): + # {{{ OnDropText(self, x, y, data) + def OnDropText(self, x, y, data): + rc = False + try: + dropMap, dropSize = json.loads(data) + viewRect = self.parent.GetViewStart() + rectX, rectY = x - (x % self.parent.backend.cellSize[0]), y - (y % self.parent.backend.cellSize[1]) + mapX, mapY = int(rectX / self.parent.backend.cellSize[0] if rectX else 0), int(rectY / self.parent.backend.cellSize[1] if rectY else 0) + mapPoint = [m + n for m, n in zip((mapX, mapY), viewRect)] + self.parent.commands.lastTool, self.parent.commands.currentTool = self.parent.commands.currentTool, ToolObject(self.parent.canvas, mapPoint, dropMap, dropSize) + self.parent.commands.update(toolName=self.parent.commands.currentTool.name) + eventDc = self.parent.backend.getDeviceContext(self.parent.GetClientSize(), self.parent, viewRect) + self.parent.applyTool(eventDc, True, None, None, self.parent.brushPos, False, False, False, self.parent.commands.currentTool, viewRect) + rc = True + except: + with wx.MessageDialog(self.parent, "Error: {}".format(sys.exc_info()[1]), "", wx.OK | wx.OK_DEFAULT) as dialog: + dialogChoice = dialog.ShowModal() + return rc + # }}} + # {{{ __init__(self, parent) + def __init__(self, parent): + super().__init__(); self.parent = parent; + # }}} class RoarCanvasWindow(GuiWindow): # {{{ _drawPatch(self, eventDc, isCursor, patch, viewRect) @@ -34,6 +61,9 @@ class RoarCanvasWindow(GuiWindow): else: self.commands.update(cellPos=mapPoint if mapPoint else self.brushPos) self.canvas.journal.end() + if rc and (tool.__class__ == ToolObject) and (tool.toolState == tool.TS_NONE): + self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool + self.commands.update(toolName=self.commands.currentTool.name) return rc # }}} # {{{ dispatchDeltaPatches(self, deltaPatches) @@ -125,5 +155,7 @@ class RoarCanvasWindow(GuiWindow): super().__init__(parent, pos, scrollStep, [w * h for w, h in zip(cellSize, size)]) self.backend, self.canvas, self.cellSize, self.commands, self.parentFrame = backend(self.size, cellSize), canvas, cellSize, commands(self, parentFrame), parentFrame self.brushColours, self.brushPos, self.brushSize, self.dirty = [4, 1], [0, 0], [1, 1], False + self.dropTarget = RoarCanvasWindowDropTarget(self) + self.SetDropTarget(self.dropTarget) # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libroar/RoarClient.py b/libroar/RoarClient.py index 8da6143..ee2bf92 100644 --- a/libroar/RoarClient.py +++ b/libroar/RoarClient.py @@ -7,6 +7,7 @@ from Canvas import Canvas from GuiCanvasWxBackend import GuiCanvasWxBackend from GuiFrame import GuiFrame, NID_TOOLBAR_HSEP +from RoarAssetsWindow import RoarAssetsWindow from RoarCanvasCommands import RoarCanvasCommands from RoarCanvasWindow import RoarCanvasWindow @@ -51,5 +52,7 @@ class RoarClient(GuiFrame): self.canvasPanel.commands.canvasTool(self.canvasPanel.commands.canvasTool, 5)(None) self.canvasPanel.commands.update(brushSize=self.canvasPanel.brushSize, colours=self.canvasPanel.brushColours) self.addWindow(self.canvasPanel, expand=True) + self.assetsWindow = RoarAssetsWindow(GuiCanvasWxBackend, defaultCellSize, self) + self.canvasPanel.commands.canvasAssetsWindowShow(None) # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolObject.py b/libtools/ToolObject.py new file mode 100644 index 0000000..151917c --- /dev/null +++ b/libtools/ToolObject.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# +# ToolObject.py +# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz +# + +from Tool import Tool + +class ToolObject(Tool): + name = "External object" + TS_NONE = 0 + TS_SELECT = 1 + TS_TARGET = 2 + + # {{{ _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, mapPoint, mouseLeftDown, mouseRightDown, selectRect, viewRect) + def _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, mapPoint, mouseLeftDown, mouseRightDown, selectRect, viewRect): + if mouseLeftDown: + disp, isCursor = [mapPoint[m] - self.lastAtPoint[m] for m in [0, 1]], True + newTargetRect = [[selectRect[n][m] + disp[m] for m in [0, 1]] for n in [0, 1]] + self.lastAtPoint = list(mapPoint) + elif mouseRightDown: + disp, isCursor, newTargetRect = [0, 0], False, selectRect.copy() + else: + disp, isCursor, newTargetRect = [0, 0], True, selectRect.copy() + dirty = self.onSelectEvent(canvas, disp, dispatchFn, eventDc, isCursor, newTargetRect, selectRect, viewRect) + self._drawSelectRect(newTargetRect, dispatchFn, eventDc, viewRect) + self.targetRect = newTargetRect + return dirty + # }}} + # {{{ _drawSelectRect(self, rect, dispatchFn, eventDc, viewRect) + def _drawSelectRect(self, rect, dispatchFn, eventDc, viewRect): + rectFrame = [[rect[m[0]][n] + m[1] for n in [0, 1]] for m in [[0, -1], [1, +1]]] + if rectFrame[0][0] > rectFrame[1][0]: + rectFrame[0][0], rectFrame[1][0] = rectFrame[1][0], rectFrame[0][0] + if rectFrame[0][1] > rectFrame[1][1]: + rectFrame[0][1], rectFrame[1][1] = rectFrame[1][1], rectFrame[0][1] + curColours = [0, 0] + for rectX in range(rectFrame[0][0], rectFrame[1][0] + 1): + curColours = [1, 1] if curColours == [0, 0] else [0, 0] + dispatchFn(eventDc, True, [rectX, rectFrame[0][1], *curColours, 0, " "], viewRect) + dispatchFn(eventDc, True, [rectX, rectFrame[1][1], *curColours, 0, " "], viewRect) + for rectY in range(rectFrame[0][1], rectFrame[1][1] + 1): + curColours = [1, 1] if curColours == [0, 0] else [0, 0] + dispatchFn(eventDc, True, [rectFrame[0][0], rectY, *curColours, 0, " "], viewRect) + dispatchFn(eventDc, True, [rectFrame[1][0], rectY, *curColours, 0, " "], viewRect) + # }}} + # {{{ _mouseEventTsNone(self, brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + def _mouseEventTsNone(self, brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect): + dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "], viewRect) + return False + # }}} + # {{{ _mouseEventTsSelect(self, brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + def _mouseEventTsSelect(self, brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect): + dirty = False + if mouseLeftDown \ + and (mapPoint[0] >= (self.targetRect[0][0] - 1)) \ + and (mapPoint[0] <= (self.targetRect[1][0] + 1)) \ + and (mapPoint[1] >= (self.targetRect[0][1] - 1)) \ + and (mapPoint[1] <= (self.targetRect[1][1] + 1)): + self.lastAtPoint, self.toolState = list(mapPoint), self.TS_TARGET + elif mouseRightDown: + dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, mapPoint, mouseLeftDown, mouseRightDown, self.targetRect, viewRect) + self.targetRect, self.toolState = None, self.TS_NONE + else: + dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, mapPoint, mouseLeftDown, mouseRightDown, self.targetRect, viewRect) + return dirty + # }}} + # {{{ _mouseEventTsTarget(self, brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + def _mouseEventTsTarget(self, brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect): + dirty = False + if mouseLeftDown: + dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, mapPoint, mouseLeftDown, mouseRightDown, self.targetRect, viewRect) + elif mouseRightDown: + dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, mapPoint, mouseLeftDown, mouseRightDown, self.targetRect, viewRect) + self.targetRect, self.toolState = None, self.TS_NONE + else: + self.toolState = self.TS_SELECT + return True, dirty + # }}} + + # + # onMouseEvent(self, brushColours, brushSize, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect): + dirty = False + if self.toolState == self.TS_NONE: + dirty = self._mouseEventTsNone(brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + elif self.toolState == self.TS_SELECT: + dirty = self._mouseEventTsSelect(brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + elif self.toolState == self.TS_TARGET: + dirty = self._mouseEventTsTarget(brushColours, canvas, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect) + else: + return False, dirty + return True, dirty + + # + # onSelectEvent(self, canvas, disp, dispatchFn, eventDc, isCursor, newTargetRect, selectRect, viewRect) + def onSelectEvent(self, canvas, disp, dispatchFn, eventDc, isCursor, newTargetRect, selectRect, viewRect): + dirty = False + for numRow in range(len(self.objectMap)): + for numCol in range(len(self.objectMap[numRow])): + rectX, rectY = selectRect[0][0] + numCol, selectRect[0][1] + numRow + dirty = False if isCursor else True + cellNew = self.objectMap[numRow][numCol] + if (cellNew[1] == -1) and (cellNew[3] == " "): + if (rectY < canvas.size[1]) and (rectX < canvas.size[0]): + cellNew = canvas.map[rectY][rectX] + dispatchFn(eventDc, isCursor, [rectX + disp[0], rectY + disp[1], *cellNew], viewRect) + return dirty + + # __init__(self, canvas, mapPoint, objectMap, objectSize): initialisation method + def __init__(self, canvas, mapPoint, objectMap, objectSize): + super().__init__() + self.lastAtPoint, self.srcRect = list(mapPoint), list(mapPoint) + self.objectMap, self.objectSize = objectMap, objectSize + self.targetRect = [list(mapPoint), [(a + b) - (0 if a == b else 1) for a, b in zip(mapPoint, objectSize)]] + self.toolSelectMap, self.toolState = [], self.TS_SELECT + for numRow in range((self.targetRect[1][1] - self.targetRect[0][1]) + 1): + self.toolSelectMap.append([]) + for numCol in range((self.targetRect[1][0] - self.targetRect[0][0]) + 1): + rectX, rectY = self.targetRect[0][0] + numCol, self.targetRect[0][1] + numRow + if (rectX < canvas.size[0]) and (rectY < canvas.size[1]): + self.toolSelectMap[numRow].append(canvas.map[rectY][rectX]) + else: + self.toolSelectMap[numRow].append([1, 1, 0, " "]) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120