summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortrustable-code <krauter.simon@arcor.de>2017-07-01 14:24:18 +0200
committertrustable-code <krauter.simon@arcor.de>2017-07-01 14:24:18 +0200
commit59aa41ad707a814ea7145248c52f6f16b1c895d3 (patch)
treed70c2142f2c9063e938af31b13e8a0eb79a5dc60
downloadNiGui-59aa41ad707a814ea7145248c52f6f16b1c895d3.tar.gz
NiGui-59aa41ad707a814ea7145248c52f6f16b1c895d3.zip
first public preview version
-rwxr-xr-xexamples/example_01_basic_app.nim61
-rwxr-xr-xexamples/example_01_basic_app.pngbin0 -> 1471 bytes
-rwxr-xr-xexamples/example_02_controls.nim34
-rwxr-xr-xexamples/example_03_layout_container.nim86
-rwxr-xr-xexamples/example_04_msgboxes.nim37
-rwxr-xr-xexamples/example_05_handle_window_close.nim15
-rwxr-xr-xexamples/example_06_keyboard_events.nim17
-rwxr-xr-xexamples/example_08_timers.nim41
-rwxr-xr-xexamples/example_09_font_size.nim25
-rwxr-xr-xexamples/example_10_drawing.nim65
-rwxr-xr-xexamples/example_11_image_processing_cli.nim47
-rw-r--r--examples/example_12_file_dialogs.nim44
-rwxr-xr-xexamples/example_90_widget_error.nim13
-rwxr-xr-xexamples/example_91_unhandled_exception.nim18
-rw-r--r--license.md9
-rwxr-xr-xreadme.md79
-rwxr-xr-xsrc/common/msgbox.nim82
-rwxr-xr-xsrc/common/nigui.nim2250
-rwxr-xr-xsrc/gtk3/gtk3.nim430
-rwxr-xr-xsrc/gtk3/nigui_platform_impl.nim1176
-rwxr-xr-xsrc/gtk3/nigui_platform_types1.nim25
-rwxr-xr-xsrc/gtk3/nigui_platform_types2.nim20
-rwxr-xr-xsrc/windows/nigui_platform_impl.nim1278
-rwxr-xr-xsrc/windows/nigui_platform_types1.nim23
-rwxr-xr-xsrc/windows/nigui_platform_types2.nim18
-rwxr-xr-xsrc/windows/windows.nim452
26 files changed, 6345 insertions, 0 deletions
diff --git a/examples/example_01_basic_app.nim b/examples/example_01_basic_app.nim
new file mode 100755
index 0000000..97109c2
--- /dev/null
+++ b/examples/example_01_basic_app.nim
@@ -0,0 +1,61 @@
+# This example shows the basic use of the NiGui toolkit.
+
+import nigui
+# First, import the library.
+
+app.init()
+# Initialization is mandatory.
+
+var window = newWindow("NiGui Example")
+# Create a window with a given title:
+# By default, a window is empty and not visible.
+# It is played at the center of the screen.
+# A window can contain only one control.
+# A container can contain multiple controls.
+
+window.width = 600
+window.height = 400
+# Set the size of the window
+
+# window.iconPath = "example_01_basic_app.png"
+# The window icon can be specified this way.
+# The default value is the name of the executable file without extension + ".png"
+
+var container = newLayoutContainer(Layout_Vertical)
+# Create a container for controls.
+# By default, a container is empty.
+# It's size will adapt to it's child controls.
+# A LayoutContainer will automatically align the child controls.
+# The layout is set to clhorizontal.
+
+window.add(container)
+# Add the container to the window.
+
+var button = newButton("Button 1")
+# Create a button with a given title.
+
+container.add(button)
+# Add the button to the container.
+
+var textArea = newTextArea()
+# Create a multiline text box.
+# By default, a text area is empty and editable.
+
+container.add(textArea)
+# Add the text area to the container.
+
+button.onClick = proc(event: ClickEvent) =
+# Set an event handler for the "onClick" event (here as anonymous proc).
+
+ textArea.addLine("Button 1 clicked, message box opened.")
+ window.alert("This is a simple message box.")
+ textArea.addLine("Message box closed.")
+
+window.show()
+# Make the window visible on the screen.
+# Controls (containers, buttons, ..) are visible by default.
+
+app.run()
+# At last, run the main loop.
+# This processes incoming events until the application quits.
+# To quit the application, dispose all windows or call "app.quit()".
diff --git a/examples/example_01_basic_app.png b/examples/example_01_basic_app.png
new file mode 100755
index 0000000..6ba3e4a
--- /dev/null
+++ b/examples/example_01_basic_app.png
Binary files differ
diff --git a/examples/example_02_controls.nim b/examples/example_02_controls.nim
new file mode 100755
index 0000000..4980ca1
--- /dev/null
+++ b/examples/example_02_controls.nim
@@ -0,0 +1,34 @@
+# This example shows several controls of NiGui.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+
+var container = newLayoutContainer(Layout_Vertical)
+window.add(container)
+
+# Add a Button control:
+var button = newButton("Button")
+container.add(button)
+
+# Add a Label control:
+var label = newLabel("Label")
+container.add(label)
+
+# Add a TextBox control:
+var textBox = newTextBox("TextBox")
+container.add(textBox)
+
+# Add a TextArea control:
+var textArea = newTextArea("TextArea\nLine 2\n")
+container.add(textArea)
+
+# Add more text to the TextArea:
+for i in 3..30:
+ textArea.addLine("Line " & $i)
+
+window.show()
+
+app.run()
diff --git a/examples/example_03_layout_container.nim b/examples/example_03_layout_container.nim
new file mode 100755
index 0000000..5310f71
--- /dev/null
+++ b/examples/example_03_layout_container.nim
@@ -0,0 +1,86 @@
+# This example shows some possibilities to align controls with a LayoutContainer.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+window.width = 800
+window.height = 600
+var mainContainer = newLayoutContainer(Layout_Vertical)
+window.add(mainContainer)
+
+# Row 1: Auto-sized:
+var innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+innerContainer.frame = newFrame("Row 1: Auto-sized")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ innerContainer.add(control)
+
+# Row 2: Auto-sized, more padding:
+innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+innerContainer.padding = 10
+innerContainer.frame = newFrame("Row 2: Auto-sized, more padding")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ innerContainer.add(control)
+
+# Row 3: Auto-sized, more spacing:
+innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+innerContainer.spacing = 15
+innerContainer.frame = newFrame("Row 3: Auto-sized, more spacing")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ innerContainer.add(control)
+
+# Row 4: Controls expanded:
+innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+innerContainer.frame = newFrame("Row 4: Controls expanded")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ control.widthMode = WidthMode_Expand
+ innerContainer.add(control)
+
+# Row 5: Controls centered:
+innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+innerContainer.widthMode = WidthMode_Expand
+innerContainer.height = 80 # problem
+innerContainer.xAlign = XAlign_Center
+innerContainer.yAlign = YAlign_Center
+innerContainer.frame = newFrame("Row 5: Controls centered")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ innerContainer.add(control)
+
+# Row 6: Container expanded, spread:
+innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+# innerContainer.height = 80
+innerContainer.widthMode = WidthMode_Expand
+innerContainer.xAlign = XAlign_Spread
+innerContainer.frame = newFrame("Row 6: Container expanded, spread")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ innerContainer.add(control)
+
+# Row 7: Static size:
+innerContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(innerContainer)
+innerContainer.widthMode = WidthMode_Expand
+innerContainer.xAlign = XAlign_Center
+innerContainer.yAlign = YAlign_Center
+innerContainer.frame = newFrame("Row 7: Static size")
+for i in 1..3:
+ var control = newButton("Button " & $i)
+ control.width = 90 * i
+ control.height = 15 * i
+ innerContainer.add(control)
+
+
+window.show()
+app.run()
diff --git a/examples/example_04_msgboxes.nim b/examples/example_04_msgboxes.nim
new file mode 100755
index 0000000..eca30a6
--- /dev/null
+++ b/examples/example_04_msgboxes.nim
@@ -0,0 +1,37 @@
+# This example shows different methods to create message boxes.
+
+import nigui
+import msgbox
+
+app.init()
+
+var window = newWindow()
+var mainContainer = newLayoutContainer(Layout_Vertical)
+window.add(mainContainer)
+
+var buttons = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(buttons)
+
+var textArea = newTextArea()
+mainContainer.add(textArea)
+
+var button1 = newButton("Example 1")
+buttons.add(button1)
+button1.onClick = proc(event: ClickEvent) =
+ window.alert("Hello.\n\nThis message box is created with \"alert()\".")
+ textArea.addLine("Message box closed")
+
+var button2 = newButton("Example 2")
+buttons.add(button2)
+button2.onClick = proc(event: ClickEvent) =
+ let res = window.msgBox("Hello.\n\nThis message box is created with \"msgBox()\".")
+ textArea.addLine("Message box closed, result = " & $res)
+
+var button3 = newButton("Example 3")
+buttons.add(button3)
+button3.onClick = proc(event: ClickEvent) =
+ let res = window.msgBox("Hello.\n\nThis message box is created with \"msgBox()\" and has three buttons.", "Title of message box", "Button 1", " Button 2", "Button 3")
+ textArea.addLine("Message box closed, result = " & $res)
+
+window.show()
+app.run()
diff --git a/examples/example_05_handle_window_close.nim b/examples/example_05_handle_window_close.nim
new file mode 100755
index 0000000..1f621d6
--- /dev/null
+++ b/examples/example_05_handle_window_close.nim
@@ -0,0 +1,15 @@
+# This example shows how to handle a window close request.
+
+import nigui
+import msgbox
+
+app.init()
+
+var window = newWindow()
+
+window.onDispose = proc(event: WindowDisposeEvent) =
+ if window.msgBox("Close the application?", "Close?", "Close", "Abort") != 1:
+ event.cancel = true
+
+window.show()
+app.run()
diff --git a/examples/example_06_keyboard_events.nim b/examples/example_06_keyboard_events.nim
new file mode 100755
index 0000000..b36713f
--- /dev/null
+++ b/examples/example_06_keyboard_events.nim
@@ -0,0 +1,17 @@
+# This example shows how to handle keyboard events.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+
+var label = newLabel()
+window.add(label)
+
+window.onKeyDown = proc(event: WindowKeyEvent) =
+ label.text = label.text & "KeyDown event: key: " & $event.key & ", unicode: " & $event.unicode & ", character: " & event.character & "\n"
+
+window.show()
+
+app.run()
diff --git a/examples/example_08_timers.nim b/examples/example_08_timers.nim
new file mode 100755
index 0000000..51c117c
--- /dev/null
+++ b/examples/example_08_timers.nim
@@ -0,0 +1,41 @@
+# This example shows how to use timers.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+
+var mainContainer = newLayoutContainer(Layout_Vertical)
+window.add(mainContainer)
+var buttonContainer = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(buttonContainer)
+
+var textArea = newTextArea()
+mainContainer.add(textArea)
+
+var timer: Timer
+var counter = 1
+
+proc timerProc(event: TimerEvent) =
+ textArea.addLine($counter)
+ counter.inc()
+
+var button1 = newButton("startTimer()")
+buttonContainer.add(button1)
+button1.onClick = proc(event: ClickEvent) =
+ timer = startTimer(500, timerProc)
+
+var button2 = newButton("startRepeatingTimer()")
+buttonContainer.add(button2)
+button2.onClick = proc(event: ClickEvent) =
+ timer = startRepeatingTimer(500, timerProc)
+
+var button3 = newButton("stopTimer()")
+buttonContainer.add(button3)
+button3.onClick = proc(event: ClickEvent) =
+ timer.stop()
+
+window.show()
+
+app.run()
diff --git a/examples/example_09_font_size.nim b/examples/example_09_font_size.nim
new file mode 100755
index 0000000..087200c
--- /dev/null
+++ b/examples/example_09_font_size.nim
@@ -0,0 +1,25 @@
+# This example shows how to change the font size of controls.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+window.width = 500
+window.height = 600
+var container = newLayoutContainer(Layout_Vertical)
+window.add(container)
+
+for i in 12..20:
+ var innerContainer = newLayoutContainer(Layout_Horizontal)
+ container.add(innerContainer)
+ innerContainer.frame = newFrame("Font size: " & $i)
+ var button = newButton("Button")
+ button.fontSize = i
+ innerContainer.add(button)
+ var label = newLabel("Label")
+ label.fontSize = i
+ innerContainer.add(label)
+
+window.show()
+app.run()
diff --git a/examples/example_10_drawing.nim b/examples/example_10_drawing.nim
new file mode 100755
index 0000000..bab3a6b
--- /dev/null
+++ b/examples/example_10_drawing.nim
@@ -0,0 +1,65 @@
+# This example shows how to draw the surface of a control.
+
+import nigui
+
+app.init()
+var window = newWindow()
+window.width = 500
+window.height = 500
+
+var control1 = newControl()
+# Creates a drawable control
+
+window.add(control1)
+
+var image1 = newImage()
+image1.loadFromFile("example_01_basic_app.png")
+# Reads the file and holds the image as bitmap in memory
+
+var image2 = newImage()
+image2.resize(2, 2)
+# Creates a new bitmap
+
+image2.canvas.setPixel(0, 0, rgb(255, 0, 0))
+image2.canvas.setPixel(0, 1, rgb(255, 0, 0))
+image2.canvas.setPixel(1, 1, rgb(0, 255, 0))
+image2.canvas.setPixel(1, 0, rgb(0, 0, 255))
+# Modifies single pixels
+
+control1.onDraw = proc (event: DrawEvent) =
+ let canvas = event.control.canvas
+ # A shortcut
+
+ canvas.areaColor = rgb(30, 30, 30) # dark grey
+ canvas.fill()
+ # Fill the whole area
+
+ canvas.setPixel(0, 0, rgb(255, 0, 0))
+ # Modifies a single pixel
+
+ canvas.areaColor = rgb(255, 0, 0) # red
+ canvas.drawRectArea(10, 10, 30, 30)
+ # Draws a filled rectangle
+
+ canvas.lineColor = rgb(255, 0, 0) # red
+ canvas.drawLine(60, 10, 110, 40)
+ # Draws a line
+
+ let text = "Hello World!"
+ canvas.textColor = rgb(0, 255, 0) # lime
+ canvas.fontSize = 20
+ canvas.fontFamily = "Arial"
+ canvas.drawText(text, 10, 70)
+ # Outputs a text
+
+ canvas.drawRectOutline(10, 70, canvas.getTextWidth(text), canvas.getTextLineHeight())
+ # Draws a rectangle outline
+
+ canvas.drawImage(image1, 10, 120)
+ # Draws an image in original size
+
+ canvas.drawImage(image2, 120, 120, 50)
+ # Draws an image stretched
+
+window.show()
+app.run()
diff --git a/examples/example_11_image_processing_cli.nim b/examples/example_11_image_processing_cli.nim
new file mode 100755
index 0000000..ec0f263
--- /dev/null
+++ b/examples/example_11_image_processing_cli.nim
@@ -0,0 +1,47 @@
+# This example shows how to do image processing without GUI
+
+import nigui
+
+app.init()
+
+var image1 = newImage()
+image1.loadFromFile("example_01_basic_app.png")
+# Reads the file and holds the image as bitmap in memory
+
+var image2 = newImage()
+image2.resize(200, 200)
+
+let canvas = image2.canvas
+# A shortcut
+
+canvas.areaColor = rgb(30, 30, 30) # dark grey
+canvas.fill()
+# Fill the whole area
+
+canvas.setPixel(0, 0, rgb(255, 0, 0))
+# Modifies a single pixel
+
+canvas.areaColor = rgb(255, 0, 0) # red
+canvas.drawRectArea(10, 10, 30, 30)
+# Draws a filled rectangle
+
+canvas.lineColor = rgb(255, 0, 0) # red
+canvas.drawLine(60, 10, 110, 40)
+# Draws a line
+
+let text = "Hello World!"
+canvas.textColor = rgb(0, 255, 0) # lime
+canvas.fontSize = 20
+canvas.fontFamily = "Arial"
+canvas.drawText(text, 10, 70)
+# Outputs a text
+
+canvas.drawRectOutline(10, 70, canvas.getTextWidth(text), canvas.getTextLineHeight())
+# Draws a rectangle outline
+
+canvas.drawImage(image1, 10, 120)
+# Draws an image in original size
+
+image2.saveToPngFile("out.png")
+# Save the image as PNG file
+
diff --git a/examples/example_12_file_dialogs.nim b/examples/example_12_file_dialogs.nim
new file mode 100644
index 0000000..b1647c3
--- /dev/null
+++ b/examples/example_12_file_dialogs.nim
@@ -0,0 +1,44 @@
+# This example shows how to use the Open File and Save File As dialogs.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+var mainContainer = newLayoutContainer(Layout_Vertical)
+window.add(mainContainer)
+
+var buttons = newLayoutContainer(Layout_Horizontal)
+mainContainer.add(buttons)
+
+var textArea = newTextArea()
+mainContainer.add(textArea)
+
+var button1 = newButton("Open ...")
+buttons.add(button1)
+button1.onClick = proc(event: ClickEvent) =
+ var dialog = newOpenFileDialog()
+ dialog.title = "Test Open"
+ dialog.multiple = true
+ dialog.directory = "/run/media/user/Data/Temp/Downloads/"
+ dialog.run()
+ textArea.addLine($dialog.files.len & " files selected")
+ if dialog.files.len > 0:
+ for file in dialog.files:
+ textArea.addLine(file)
+
+var button2 = newButton("Save as ...")
+buttons.add(button2)
+button2.onClick = proc(event: ClickEvent) =
+ var dialog = SaveFileDialog()
+ dialog.title = "Test Save"
+ dialog.directory = "/run/media/user/Data/Temp/Downloads/"
+ dialog.defaultName = "default.txt"
+ dialog.run()
+ if dialog.file == "":
+ textArea.addLine("No path selected")
+ else:
+ textArea.addLine(dialog.file)
+
+window.show()
+app.run()
diff --git a/examples/example_90_widget_error.nim b/examples/example_90_widget_error.nim
new file mode 100755
index 0000000..551ee8a
--- /dev/null
+++ b/examples/example_90_widget_error.nim
@@ -0,0 +1,13 @@
+# This example shows what happens when a nigui widget error occurs.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+
+window.add(newButton("Button 1")) # ok
+window.add(newButton("Button 2")) # not ok, gives error messages, but program continues
+
+window.show()
+app.run()
diff --git a/examples/example_91_unhandled_exception.nim b/examples/example_91_unhandled_exception.nim
new file mode 100755
index 0000000..d9f46c0
--- /dev/null
+++ b/examples/example_91_unhandled_exception.nim
@@ -0,0 +1,18 @@
+# This example shows what happens when an unhandled exception occurs in the main loop.
+
+import nigui
+
+app.init()
+
+var window = newWindow()
+
+var container = newLayoutContainer()
+window.add(container)
+
+var button = newButton("Raise Exception")
+container.add(button)
+button.onClick = proc(event: ClickEvent) =
+ raise newException(Exception, "Test Exception")
+
+window.show()
+app.run()
diff --git a/license.md b/license.md
new file mode 100644
index 0000000..5c2debf
--- /dev/null
+++ b/license.md
@@ -0,0 +1,9 @@
+Copyright 2017 Simon Krauter
+
+[MIT License](https://opensource.org/licenses/MIT)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/readme.md b/readme.md
new file mode 100755
index 0000000..bcfe1c5
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,79 @@
+NiGui
+=====
+
+NiGui is a cross-platform, desktop GUI toolkit written in Nim.<br>
+NiGui provides an easy way to develop applications in Nim with a full-featured graphical user interface.
+
+Target platforms:
+* Linux over GTK+ 3
+* Windows (Win32 API)
+* macOS (planned)
+
+Design goals:
+* **Full abstraction**<br>
+NiGui provides full abstraction of the underlying platform. NiGui applications are written once and can be compiled for different platforms. Application developers don't have to care about platform-specific details.
+* **Simple and elegant**<br>
+NiGui has a clean and beginner-friendly high-level API. It is much less complex than the Win32 API, GTK+ or Qt.<br>
+NiGui profits of Nim's features and elegance in contrast to C code, for example Nim's polymorphism capabilities.
+* **Powerful**<br>
+NiGui uses the native controls of the underlying platform to give a familiar use and feel for the user. In addtion, NiGui allows to create custom controls for special use cases or a themed UI. <br>
+NiGui has it's own layout manager for automatic resizing and positioning of controls.
+* **Minimal dependencies**<br>
+The NiGui source code has no dependencies except Nim's standard library. Platform bindings are included.<br>
+Generated binaries (exe files) include NiGui and do not need external libraries.
+
+Current state
+-------------
+NiGui is currently work in progress. Very basic things work, many things are missing.
+
+Working:
+* Window, Button, Label, TextBox, TextArea
+* LayoutContainer (own layout manager)
+* Timers
+* Message boxes and file dialogs
+* Custom controls including scrolling
+* Drawing and image processing
+
+WIP:
+* Event handling
+* Documentation
+
+Planned:
+* macOS support
+* More widgets
+
+Getting started
+---------------
+Recommended steps to use NiGui:
+1. Clone the NiGui repository
+2. Add the follwing two include paths to your Nim configuration:
+
+For Linux/Gtk:
+```
+--path:"<path_to_nigui>/src/common"
+--path:"<path_to_nigui>/src/gtk3"
+```
+
+For Windows:
+```
+--path:"<path_to_nigui>/src/common"
+--path:"<path_to_nigui>/src/windows"
+```
+
+3. Try the included example programs
+
+Contributing
+-----------
+You can help to improve NiGui by:
+* Trying to use it and giving feedback
+* Test the programs under different Windows versions or Linux distributions
+* Developing show cases
+* Help improving and extending the code
+* Adding macOS support
+
+Contact: simonkrauter@openmailbox.org
+
+License
+-------
+
+NiGui is FLOSS (free and open-source software) licensed under the [MIT License](https://opensource.org/licenses/MIT). As a result you may use any compatible license (essentially any license) for your own programs developed with NiGui. You are explicitly permitted to develop commercial applications using NiGui.
diff --git a/src/common/msgbox.nim b/src/common/msgbox.nim
new file mode 100755
index 0000000..5071dfb
--- /dev/null
+++ b/src/common/msgbox.nim
@@ -0,0 +1,82 @@
+# NiGui - extended message box
+
+# This module provides an extended message box.
+# The message box is shown as modal window.
+# The message box can have up to 3 buttons with customizable titles.
+# Call the proc msgBox() to open the message box.
+# It will wait until the message box is closed.
+# Meaning of the result value:
+# 0 - message box was closed over the window close button
+# 1..3 - button 1..3 clicked
+
+# For an example see "example_04_msgboxes.nim".
+
+import nigui
+
+type MessageBoxWindow = ref object of WindowImpl
+ clickedButton: Button
+
+proc buttonClick(event: ClickEvent) =
+ cast[MessageBoxWindow](event.control.parentWindow).clickedButton = cast[Button](event.control)
+ event.control.parentWindow.dispose()
+
+proc msgBox*(parent: Window, message: string, title = "Message", button1 = "OK", button2, button3: string = nil): int {.discardable.} =
+ const buttonMinWidth = 100
+ var window = new MessageBoxWindow
+ window.init()
+ window.title = title
+
+ var container = newLayoutContainer(Layout_Vertical)
+ container.padding = 10
+ window.control = container
+
+ var labelContainer = newLayoutContainer(Layout_Horizontal)
+ container.add(labelContainer)
+ labelContainer.widthMode = WidthMode_Expand
+ labelContainer.heightMode = HeightMode_Expand
+
+ var label = newLabel(message)
+ labelContainer.add(label)
+
+ var buttonContainer = newLayoutContainer(Layout_Horizontal)
+ buttonContainer.widthMode = WidthMode_Expand
+ buttonContainer.xAlign = XAlign_Center
+ buttonContainer.spacing = 12
+ container.add(buttonContainer)
+ var b1, b2, b3: Button
+
+ b1 = newButton(button1)
+ b1.minWidth = buttonMinWidth
+ b1.onClick = buttonClick
+ buttonContainer.add(b1)
+
+ if button2 != nil:
+ b2 = newButton(button2)
+ b2.minWidth = buttonMinWidth
+ b2.onClick = buttonClick
+ buttonContainer.add(b2)
+
+ if button3 != nil:
+ b3 = newButton(button3)
+ b3.minWidth = buttonMinWidth
+ b3.onClick = buttonClick
+ buttonContainer.add(b3)
+
+ window.width = min(max(label.width + 40, buttonMinWidth * 3 + 65), 600)
+ window.height = min(label.height, 300) + buttonContainer.height + 70
+
+ # Center message box on window:
+ window.x = parent.x + ((parent.width - window.width) div 2)
+ window.y = parent.y + ((parent.height - window.height) div 2)
+
+ window.showModal(parent)
+
+ while not window.disposed:
+ app.sleep(100)
+
+ if window.clickedButton == b1:
+ result = 1
+ elif window.clickedButton == b2:
+ result = 2
+ elif window.clickedButton == b3:
+ result = 3
diff --git a/src/common/nigui.nim b/src/common/nigui.nim
new file mode 100755
index 0000000..c81c02d
--- /dev/null
+++ b/src/common/nigui.nim
@@ -0,0 +1,2250 @@
+# NiGui - main file
+
+# This file contains all common code except extra widgets.
+# All public procedures are declared here.
+# Public types are declared here or in the platform-specific file "nigui_platform_types".
+# The platform-specific files "nigui_platform_types" and "nigui_platform_procs" will be
+# included to this file.
+
+# ========================================================================================
+#
+# Public Declaration
+#
+# ========================================================================================
+
+# ----------------------------------------------------------------------------------------
+# Simple Types
+# ----------------------------------------------------------------------------------------
+
+type
+ Layout* = enum
+ Layout_Horizontal
+ Layout_Vertical
+
+ XAlign* = enum
+ XAlign_Left
+ XAlign_Right
+ XAlign_Center
+ XAlign_Spread
+
+ YAlign* = enum
+ YAlign_Top
+ YAlign_Bottom
+ YAlign_Center
+ YAlign_Spread
+
+ WidthMode* = enum
+ WidthMode_Static
+ WidthMode_Auto
+ WidthMode_Fill
+ WidthMode_Expand
+
+ HeightMode* = enum
+ HeightMode_Static
+ HeightMode_Auto
+ HeightMode_Fill
+ HeightMode_Expand
+
+ MouseButton* = enum
+ MouseButton_Left
+ MouseButton_Middle
+ MouseButton_Right
+
+ Color* = object
+ red*: byte
+ green*: byte
+ blue*: byte
+ alpha*: byte
+
+ Spacing* = object
+ left*: int
+ right*: int
+ top*: int
+ bottom*: int
+
+ Timer* = distinct int
+
+ Key* = enum
+ Key_None
+ Key_Number0
+ Key_Number1
+ Key_Number2
+ Key_Number3
+ Key_Number4
+ Key_Number5
+ Key_Number6
+ Key_Number7
+ Key_Number8
+ Key_Number9
+ Key_A
+ Key_B
+ Key_C
+ Key_D
+ Key_E
+ Key_F
+ Key_G
+ Key_H
+ Key_I
+ Key_J
+ Key_K
+ Key_L
+ Key_M
+ Key_N
+ Key_O
+ Key_P
+ Key_Q
+ Key_R
+ Key_S
+ Key_T
+ Key_U
+ Key_V
+ Key_W
+ Key_X
+ Key_Y
+ Key_Z
+ Key_Space
+ Key_Tab
+ Key_Return
+ Key_Escape
+ Key_Insert
+ Key_Delete
+ Key_Backspace
+ Key_Left
+ Key_Right
+ Key_Up
+ Key_Down
+ Key_Home
+ Key_End
+ Key_PageUp
+ Key_PageDown
+
+const inactiveTimer* = 0
+
+
+# ----------------------------------------------------------------------------------------
+# Widget Types 1/3
+# ----------------------------------------------------------------------------------------
+
+type
+ # Window base type:
+
+ Window* = ref object of RootObj
+ fDisposed: bool
+ fTitle: string
+ fVisible: bool
+ fWidth, fHeight: int
+ fClientWidth, fClientHeight: int
+ fX, fY: int
+ fControl: Control
+ fIconPath: string
+ fOnDispose: WindowDisposeProc
+ fOnResize: ResizeProc
+ fOnDropFiles: DropFilesProc
+ fOnKeyDown: WindowKeyProc
+
+ # Control base type:
+
+ Control* = ref object of RootObj
+ fDisposed: bool
+ fParentControl: Control # is nil or object of ContainerImpl
+ fParentWindow: Window # only set for top level widget
+ fIndex: int
+ fVisible: bool
+ fWidth, fHeight: int
+ fX, fY: int
+ fWidthMode: WidthMode
+ fHeightMode: HeightMode
+ fMinWidth, fMinHeight: int
+ fMaxWidth, fMaxHeight: int
+ fXScrollEnabled, fYScrollEnabled: bool
+ fXScrollPos, fYScrollPos: int
+ fScrollableWidth, fScrollableHeight: int
+ fFontFamily: string
+ fFontSize: int
+ fTextColor: Color
+ fBackgroundColor: Color
+ fUseDefaultFontFamily: bool
+ fUseDefaultFontSize: bool
+ fUseDefaultTextColor: bool
+ fUseDefaultBackgroundColor: bool
+ fCanvas: Canvas
+ fOnDispose: ControlDisposeProc
+ fOnDraw: DrawProc
+ fOnMouseButtonDown: MouseButtonProc
+ fOnMouseButtonUp: MouseButtonProc
+ fOnClick: ClickProc
+ # fOnMouseMove: MouseMoveProc
+ fOnKeyDown: ControlKeyProc
+ fOnTextChange: TextChangeProc
+ tag*: string
+
+ # Drawing:
+
+ Canvas* = ref object of RootObj
+ fWidth: int
+ fHeight: int
+ fFontFamily: string
+ fFontSize: int
+ fTextColor: Color
+ fLineColor: Color
+ fAreaColor: Color
+
+ Image* = ref object of RootObj
+ fCanvas: Canvas
+
+ # Window events:
+
+ WindowDisposeEvent* = ref object
+ window*: Window
+ cancel*: bool
+ WindowDisposeProc* = proc(event: WindowDisposeEvent)
+
+ ResizeEvent* = ref object
+ window*: Window
+ ResizeProc* = proc(event: ResizeEvent)
+
+ DropFilesEvent* = ref object
+ window*: Window
+ files*: seq[string]
+ DropFilesProc* = proc(event: DropFilesEvent)
+
+ WindowKeyEvent* = ref object
+ window*: Window
+ key*: Key
+ unicode*: int
+ character*: string # UTF-8 character
+ WindowKeyProc* = proc(event: WindowKeyEvent)
+
+ # Control events:
+
+ ControlDisposeEvent* = ref object
+ control*: Control
+ cancel*: bool
+ ControlDisposeProc* = proc(event: ControlDisposeEvent)
+
+ DrawEvent* = ref object
+ control*: Control
+ DrawProc* = proc(event: DrawEvent)
+
+ MouseButtonEvent* = ref object
+ control*: Control
+ button*: MouseButton
+ x*: int
+ y*: int
+ MouseButtonProc* = proc(event: MouseButtonEvent)
+
+ ClickEvent* = ref object
+ control*: Control
+ ClickProc* = proc(event: ClickEvent)
+
+ ControlKeyEvent* = ref object
+ control*: Control
+ key*: Key
+ unicode*: int
+ character*: string # UTF-8 character
+ cancel*: bool
+ ControlKeyProc* = proc(event: ControlKeyEvent)
+
+ TextChangeEvent* = ref object
+ control*: Control
+ TextChangeProc* = proc(event: TextChangeEvent)
+
+ # Other events:
+
+ ErrorHandlerProc* = proc()
+
+ TimerEvent* = ref object
+ timer*: Timer
+ data*: pointer
+ TimerProc* = proc(event: TimerEvent)
+
+
+# Platform-specific extension of Window and Control:
+include nigui_platform_types1
+
+
+# ----------------------------------------------------------------------------------------
+# Widget Types 2/3
+# ----------------------------------------------------------------------------------------
+
+type
+ # Basic controls:
+
+ Container* = ref object of ControlImpl
+ fFrame: Frame
+ fChildControls: seq[Control]
+
+ Frame* = ref object of ControlImpl
+ fText: string
+
+ Button* = ref object of ControlImpl
+ fText: string
+
+ Label* = ref object of ControlImpl
+ fText: string
+
+ TextBox* = ref object of ControlImpl
+
+ TextArea* = ref object of ControlImpl
+ fWrap: bool
+
+
+# Platform-specific extension of basic controls:
+include nigui_platform_types2
+
+
+# ----------------------------------------------------------------------------------------
+# Widget Types 3/3
+# ----------------------------------------------------------------------------------------
+
+type
+ LayoutContainer* = ref object of ContainerImpl
+ fLayout: Layout
+ fXAlign: XAlign
+ fYAlign: YAlign
+ fPadding: int
+ fSpacing: int
+
+
+# ----------------------------------------------------------------------------------------
+# Global Variables
+# ----------------------------------------------------------------------------------------
+
+var quitOnLastWindowClose* = true
+var clickMaxXYMove* = 20
+
+# dummy type and object, needed to use get/set properties
+type App = object
+var app*: App
+
+
+# ----------------------------------------------------------------------------------------
+# Global/App Procedures
+# ----------------------------------------------------------------------------------------
+
+proc init*(app: App)
+
+proc run*(app: App)
+
+proc quit*(app: App)
+
+proc processEvents*(app: App)
+
+proc sleep*(app: App, milliSeconds: float)
+
+proc errorHandler*(app: App): ErrorHandlerProc
+proc `errorHandler=`*(app: App, errorHandler: ErrorHandlerProc)
+
+proc defaultBackgroundColor*(app: App): Color
+proc `defaultBackgroundColor=`*(app: App, color: Color)
+
+proc defaultTextColor*(app: App): Color
+proc `defaultTextColor=`*(app: App, color: Color)
+
+proc defaultFontFamily*(app: App): string
+proc `defaultFontFamily=`*(app: App, fontFamily: string)
+
+proc defaultFontSize*(app: App): int
+proc `defaultFontSize=`*(app: App, fontSize: int)
+
+proc rgb*(red, green, blue: byte, alpha: byte = 255): Color
+
+
+# ----------------------------------------------------------------------------------------
+# Dialogs
+# ----------------------------------------------------------------------------------------
+
+proc alert*(window: Window, message: string, title = "Message") {.discardable.}
+
+type OpenFileDialog* = ref object
+ title*: string
+ directory*: string
+ multiple*: bool
+ files*: seq[string]
+
+proc newOpenFileDialog*(): OpenFileDialog
+
+method run*(dialog: OpenFileDialog)
+
+type SaveFileDialog* = ref object
+ title*: string
+ directory*: string
+ defaultExtension*: string
+ defaultName*: string
+ file*: string
+
+proc newSaveFileDialog*(): SaveFileDialog
+
+method run*(dialog: SaveFileDialog)
+
+
+# ----------------------------------------------------------------------------------------
+# Timers
+# ----------------------------------------------------------------------------------------
+
+proc startTimer*(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer {.discardable.}
+
+proc startRepeatingTimer*(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer {.discardable.}
+
+proc stop*(timer: var Timer)
+
+
+# ----------------------------------------------------------------------------------------
+# Canvas
+# ----------------------------------------------------------------------------------------
+
+proc newCanvas*(control: Control = nil): CanvasImpl
+
+method destroy*(canvas: Canvas)
+
+method width*(canvas: Canvas): int
+
+method height*(canvas: Canvas): int
+
+method fontFamily*(canvas: Canvas): string
+method `fontFamily=`*(canvas: Canvas, fontFamily: string)
+
+method fontSize*(canvas: Canvas): int
+method `fontSize=`*(canvas: Canvas, fontSize: int)
+
+method textColor*(canvas: Canvas): Color
+method `textColor=`*(canvas: Canvas, color: Color)
+
+method lineColor*(canvas: Canvas): Color
+method `lineColor=`*(canvas: Canvas, color: Color)
+
+method areaColor*(canvas: Canvas): Color
+method `areaColor=`*(canvas: Canvas, color: Color)
+
+method drawText*(canvas: Canvas, text: string, x, y = 0)
+
+method drawTextCentered*(canvas: Canvas, text: string, x, y = 0, width, height = -1)
+
+method drawLine*(canvas: Canvas, x1, y1, x2, y2: int)
+
+method drawRectArea*(canvas: Canvas, x, y, width, height: int)
+
+method drawRectOutline*(canvas: Canvas, x, y, width, height: int)
+
+method fill*(canvas: Canvas)
+
+method drawImage*(canvas: Canvas, image: Image, x, y = 0, width, height = -1)
+
+method setPixel*(canvas: Canvas, x, y: int, color: Color)
+
+method getTextLineWidth*(canvas: Canvas, text: string): int
+
+method getTextLineHeight*(canvas: Canvas): int
+
+method getTextWidth*(canvas: Canvas, text: string): int
+
+
+# ----------------------------------------------------------------------------------------
+# Image
+# ----------------------------------------------------------------------------------------
+
+proc newImage*(): Image
+
+method resize*(image: Image, width, height: int)
+
+method loadFromFile*(image: Image, filePath: string)
+
+method saveToPngFile*(image: Image, filePath: string)
+
+method saveToJpegFile*(image: Image, filePath: string, quality = 80)
+
+method width*(image: Image): int
+
+method height*(image: Image): int
+
+method canvas*(image: Image): Canvas
+
+
+# ----------------------------------------------------------------------------------------
+# Window
+# ----------------------------------------------------------------------------------------
+
+proc newWindow*(title: string = nil): Window
+## Constructor for a Window object.
+## If the title is nil, it will be set to the application filename.
+
+proc init*(window: WindowImpl)
+## Initialize a WindowImpl object
+## Only needed for own constructors.
+
+proc dispose*(window: var Window)
+proc dispose*(window: Window)
+
+proc disposed*(window: Window): bool
+
+method visible*(window: Window): bool
+method `visible=`*(window: Window, visible: bool)
+
+method show*(window: Window)
+
+method showModal*(window: Window, parent: Window)
+
+method hide*(window: Window)
+
+method control*(window: Window): Control
+method `control=`*(window: Window, control: Control)
+
+method add*(window: Window, control: Control)
+
+method title*(window: Window): string
+method `title=`*(window: Window, title: string)
+
+method x*(window: Window): int
+method `x=`*(window: Window, x: int)
+
+method y*(window: Window): int
+method `y=`*(window: Window, y: int)
+
+method centerOnScreen*(window: Window)
+
+method width*(window: Window): int
+method `width=`*(window: Window, width: int)
+
+method height*(window: Window): int
+method `height=`*(window: Window, height: int)
+
+method clientWidth*(window: Window): int
+
+method clientHeight*(window: Window): int
+
+method iconPath*(window: Window): string
+method `iconPath=`*(window: Window, iconPath: string)
+
+method handleDisposeEvent*(window: Window, event: WindowDisposeEvent)
+
+method handleResizeEvent*(window: Window, event: ResizeEvent)
+
+method handleKeyDownEvent*(window: Window, event: WindowKeyEvent)
+
+method handleDropFilesEvent*(window: Window, event: DropFilesEvent)
+
+method onDispose*(window: Window): WindowDisposeProc
+method `onDispose=`*(window: Window, callback: WindowDisposeProc)
+
+method onResize*(window: Window): ResizeProc
+method `onResize=`*(window: Window, callback: ResizeProc)
+
+method onDropFiles*(window: Window): DropFilesProc
+method `onDropFiles=`*(window: Window, callback: DropFilesProc)
+
+method onKeyDown*(window: Window): WindowKeyProc
+method `onKeyDown=`*(window: Window, callback: WindowKeyProc)
+
+
+# ----------------------------------------------------------------------------------------
+# Control
+# ----------------------------------------------------------------------------------------
+
+proc newControl*(): Control
+
+proc init*(control: Control)
+proc init*(control: ControlImpl)
+
+proc dispose*(control: var Control)
+proc dispose*(control: Control)
+
+proc disposed*(control: Control): bool
+
+method visible*(control: Control): bool
+method `visible=`*(control: Control, visible: bool)
+method show*(control: Control)
+method hide*(control: Control)
+
+# Allow the outside to walk over child widgets
+method childControls*(control: Control): seq[Control]
+
+method parentControl*(control: Control): Control
+
+method parentWindow*(control: Control): WindowImpl
+
+method width*(control: Control): int
+# Set the control's width to a fixed value (sets widthMode to fixed)
+method `width=`*(control: Control, width: int)
+
+method height*(control: Control): int
+# Set the control's height to a fixed value (sets heightMode to fixed)
+method `height=`*(control: Control, height: int)
+
+method minWidth*(control: Control): int
+method `minWidth=`*(control: Control, minWidth: int)
+
+method minHeight*(control: Control): int
+method `minHeight=`*(control: Control, minHeight: int)
+
+method maxWidth*(control: Control): int
+method `maxWidth=`*(control: Control, maxWidth: int)
+
+method maxHeight*(control: Control): int
+method `maxHeight=`*(control: Control, maxHeight: int)
+
+# Set the control's width and height without changing widthMode or heightMode
+method setSize*(control: Control, width, height: int)
+
+method x*(control: Control): int
+method `x=`*(control: Control, x: int)
+
+method y*(control: Control): int
+method `y=`*(control: Control, y: int)
+
+method setPosition*(control: Control, x, y: int)
+
+method naturalWidth*(control: Control): int
+
+method naturalHeight*(control: Control): int
+
+method wantedWidth*(control: Control): int
+
+method wantedHeight*(control: Control): int
+
+method focus*(control: Control)
+
+method getTextLineWidth*(control: Control, text: string): int
+
+method getTextLineHeight*(control: Control): int
+
+method getTextWidth*(control: Control, text: string): int
+
+method `widthMode=`*(control: Control, mode: WidthMode)
+method widthMode*(control: Control): WidthMode
+
+method heightMode*(control: Control): HeightMode
+method `heightMode=`*(control: Control, mode: HeightMode)
+
+method visibleWidth*(control: Control): int
+
+method visibleHeight*(control: Control): int
+
+method xScrollPos*(control: Control): int
+method `xScrollPos=`*(control: Control, xScrollPos: int)
+
+method yScrollPos*(control: Control): int
+method `yScrollPos=`*(control: Control, yScrollPos: int)
+
+method scrollableWidth*(control: Control): int
+method `scrollableWidth=`*(control: Control, scrollableWidth: int)
+
+method scrollableHeight*(control: Control): int
+method `scrollableHeight=`*(control: Control, scrollableHeight: int)
+
+method fontFamily*(control: Control): string
+method `fontFamily=`*(control: Control, fontFamily: string)
+method setFontFamily*(control: Control, fontFamily: string)
+method resetFontFamily*(control: Control)
+
+method fontSize*(control: Control): int
+method `fontSize=`*(control: Control, fontSize: int)
+method setFontSize*(control: Control, fontSize: int)
+method resetFontSize*(control: Control)
+
+method backgroundColor*(control: Control): Color
+method `backgroundColor=`*(control: Control, color: Color)
+method setBackgroundColor*(control: Control, color: Color)
+method resetBackgroundColor*(control: Control)
+
+method textColor*(control: Control): Color
+method `textColor=`*(control: Control, color: Color)
+method setTextColor*(control: Control, color: Color)
+method resetTextColor*(control: Control)
+
+method forceRedraw*(control: Control)
+
+method canvas*(control: Control): Canvas
+
+method handleDisposeEvent*(control: Control, event: ControlDisposeEvent)
+
+method handleDrawEvent*(control: Control, event: DrawEvent)
+
+method handleMouseButtonDownEvent*(control: Control, event: MouseButtonEvent)
+
+method handleMouseButtonUpEvent*(control: Control, event: MouseButtonEvent)
+
+method handleClickEvent*(control: Control, event: ClickEvent)
+
+method handleKeyDownEvent*(control: Control, event: ControlKeyEvent)
+
+method handleTextChangeEvent*(control: Control, event: TextChangeEvent)
+
+method onDispose*(control: Control): ControlDisposeProc
+method `onDispose=`*(control: Control, callback: ControlDisposeProc)
+
+method onDraw*(control: Control): DrawProc
+method `onDraw=`*(control: Control, callback: DrawProc)
+
+method onMouseButtonDown*(control: Control): MouseButtonProc
+method `onMouseButtonDown=`*(control: Control, callback: MouseButtonProc)
+
+method onMouseButtonUp*(control: Control): MouseButtonProc
+method `onMouseButtonUp=`*(control: Control, callback: MouseButtonProc)
+
+method onClick*(control: Control): ClickProc
+method `onClick=`*(control: Control, callback: ClickProc)
+
+method onKeyDown*(control: Control): ControlKeyProc
+method `onKeyDown=`*(control: Control, callback: ControlKeyProc)
+
+method onTextChange*(control: Control): TextChangeProc
+method `onTextChange=`*(control: Control, callback: TextChangeProc)
+
+
+# ----------------------------------------------------------------------------------------
+# Container
+# ----------------------------------------------------------------------------------------
+
+proc newContainer*(): Container
+
+proc init*(container: Container)
+proc init*(container: ContainerImpl)
+
+method frame*(container: Container): Frame
+method `frame=`*(container: Container, frame: Frame)
+
+method add*(container: Container, control: Control)
+method remove*(container: Container, control: Control)
+
+method getPadding*(container: Container): Spacing
+
+method setInnerSize*(container: Container, width, height: int)
+
+
+# ----------------------------------------------------------------------------------------
+# LayoutContainer
+# ----------------------------------------------------------------------------------------
+
+proc newLayoutContainer*(layout: Layout): LayoutContainer
+
+method layout*(container: LayoutContainer): Layout
+method `layout=`*(container: LayoutContainer, layout: Layout)
+
+method xAlign*(container: LayoutContainer): XAlign
+method `xAlign=`*(container: LayoutContainer, xAlign: XAlign)
+
+method yAlign*(container: LayoutContainer): YAlign
+method `yAlign=`*(container: LayoutContainer, yAlign: YAlign)
+
+method padding*(container: LayoutContainer): int
+method `padding=`*(container: LayoutContainer, padding: int)
+
+method spacing*(container: LayoutContainer): int
+method `spacing=`*(container: LayoutContainer, spacing: int)
+
+
+# ----------------------------------------------------------------------------------------
+# Frame
+# ----------------------------------------------------------------------------------------
+
+proc newFrame*(text = ""): Frame
+
+proc init*(frame: Frame)
+proc init*(frame: NativeFrame)
+
+method text*(frame: Frame): string
+method `text=`*(frame: Frame, text: string)
+
+method getPadding*(frame: Frame): Spacing
+
+
+# ----------------------------------------------------------------------------------------
+# Button
+# ----------------------------------------------------------------------------------------
+
+proc newButton*(text = ""): Button
+
+proc init*(button: Button)
+proc init*(button: NativeButton)
+
+method text*(button: Button): string
+method `text=`*(button: Button, text: string)
+
+
+# ----------------------------------------------------------------------------------------
+# Label
+# ----------------------------------------------------------------------------------------
+
+proc newLabel*(text = ""): Label
+
+proc init*(label: Label)
+proc init*(label: NativeLabel)
+
+method text*(label: Label): string
+method `text=`*(label: Label, text: string)
+
+
+# ----------------------------------------------------------------------------------------
+# TextBox
+# ----------------------------------------------------------------------------------------
+
+proc newTextBox*(text = ""): TextBox
+
+proc init*(textBox: TextBox)
+proc init*(textBox: NativeTextBox)
+
+method text*(textBox: TextBox): string
+method `text=`*(textBox: TextBox, text: string)
+
+
+# ----------------------------------------------------------------------------------------
+# TextArea
+# ----------------------------------------------------------------------------------------
+
+proc newTextArea*(text = ""): TextArea
+
+proc init*(textArea: TextArea)
+proc init*(textArea: NativeTextArea)
+
+method text*(textArea: TextArea): string
+method `text=`*(textArea: TextArea, text: string)
+method addText*(textArea: TextArea, text: string)
+method addLine*(textArea: TextArea, text = "")
+
+method scrollToBottom*(textArea: TextArea)
+
+method wrap*(textArea: TextArea): bool
+method `wrap=`*(textArea: TextArea, wrap: bool)
+
+
+# ----------------------------------------------------------------------------------------
+# Private Procedures Predeclaration
+# ----------------------------------------------------------------------------------------
+
+proc raiseError(msg: string, showAlert = true, title = "NiGui Error")
+
+proc handleException()
+
+proc runMainLoop()
+
+proc init(window: Window)
+
+method destroy(window: Window)
+
+proc triggerRelayout(window: Window)
+
+method destroy(control: Control)
+
+proc triggerRelayout(control: Control)
+
+proc triggerRelayoutIfModeIsAuto(control: Control)
+
+method relayout(control: Control, availableWidth, availableHeight: int)
+
+method realignChildControls(control: Control)
+
+method setControlPosition(container: Container, control: Control, x, y: int)
+
+proc countLines(s: string): int
+
+
+# ========================================================================================
+#
+# Implementation
+#
+# ========================================================================================
+
+import math
+import os
+import strutils
+import times
+
+
+# ----------------------------------------------------------------------------------------
+# Global Variables
+# ----------------------------------------------------------------------------------------
+
+var fErrorHandler: ErrorHandlerProc = nil
+var windowList: seq[Window] = @[]
+var fScrollbarSize = -1
+
+# Default style:
+var fDefaultBackgroundColor: Color # initialized by platform-specific init()
+var fDefaultTextColor: Color # initialized by platform-specific init()
+var fDefaultFontFamily = ""
+var fDefaultFontSize = 15
+
+
+# ----------------------------------------------------------------------------------------
+# Global/App Procedures
+# ----------------------------------------------------------------------------------------
+
+proc raiseError(msg: string, showAlert = true, title = "NiGui Error") =
+ if showAlert:
+ alert(nil, msg & "\n\n" & getStackTrace(), title)
+ raise newException(Exception, msg)
+
+proc handleException() =
+ if fErrorHandler == nil:
+ raiseError(getCurrentExceptionMsg(), true, "Unhandled Exception")
+ else:
+ fErrorHandler()
+
+proc rgb(red, green, blue: byte, alpha: byte = 255): Color =
+ result.red = red
+ result.green = green
+ result.blue = blue
+ result.alpha = alpha
+
+proc countLines(s: string): int = strutils.countLines(s) + 1
+
+proc sleep(app: App, milliSeconds: float) =
+ let t = epochTime() + milliSeconds / 1000
+ while epochTime() < t:
+ app.processEvents()
+ os.sleep(20)
+
+proc run(app: App) =
+ while true:
+ try:
+ runMainLoop()
+ break
+ except:
+ handleException()
+
+proc quit(app: App) = quit()
+
+proc errorHandler(app: App): ErrorHandlerProc = fErrorHandler
+
+proc `errorHandler=`(app: App, errorHandler: ErrorHandlerProc) = fErrorHandler = errorHandler
+
+proc defaultBackgroundColor(app: App): Color = fDefaultBackgroundColor
+
+proc updateBackgroundColor(control: Control) =
+ if control.fUseDefaultBackgroundColor and control.backgroundColor != fDefaultBackgroundColor:
+ control.setBackgroundColor(fDefaultBackgroundColor)
+ for child in control.childControls:
+ child.updateBackgroundColor()
+
+proc `defaultBackgroundColor=`(app: App, color: Color) =
+ fDefaultBackgroundColor = color
+ for window in windowList:
+ let control = window.control
+ if control != nil:
+ control.updateBackgroundColor()
+
+proc defaultTextColor(app: App): Color = fDefaultTextColor
+
+proc updateTextColor(control: Control) =
+ if control.fUseDefaultTextColor and control.textColor != fDefaultTextColor:
+ control.setTextColor(fDefaultTextColor)
+ for child in control.childControls:
+ child.updateTextColor()
+
+proc `defaultTextColor=`(app: App, color: Color) =
+ fDefaultTextColor = color
+ for window in windowList:
+ let control = window.control
+ if control != nil:
+ control.updateTextColor()
+
+proc defaultFontFamily(app: App): string = fDefaultFontFamily
+
+proc updateFontFamily(control: Control) =
+ if control.fUseDefaultFontFamily and control.fontFamily != fDefaultFontFamily:
+ control.setFontFamily(fDefaultFontFamily)
+ for child in control.childControls:
+ child.updateFontFamily()
+
+proc `defaultFontFamily=`(app: App, fontFamily: string) =
+ fDefaultFontFamily = fontFamily
+ for window in windowList:
+ let control = window.control
+ if control != nil:
+ control.updateFontFamily()
+
+proc defaultFontSize(app: App): int = fDefaultFontSize
+
+proc updateFontSize(control: Control) =
+ if control.fUseDefaultFontSize and control.fontSize != fDefaultFontSize:
+ control.setFontSize(fDefaultFontSize)
+ for child in control.childControls:
+ child.updateFontSize()
+
+proc `defaultFontSize=`(app: App, fontSize: int) =
+ fDefaultFontSize = fontSize
+ for window in windowList:
+ let control = window.control
+ if control != nil:
+ control.updateFontSize()
+
+proc newOpenFileDialog(): OpenFileDialog =
+ result = new OpenFileDialog
+ result.title = "Open File"
+ result.directory = getCurrentDir()
+ result.files = @[]
+
+proc newSaveFileDialog(): SaveFileDialog =
+ result = new SaveFileDialog
+ result.title = "Save File"
+ result.directory = getCurrentDir()
+ result.defaultExtension = ""
+ result.defaultName = ""
+ result.file = ""
+
+
+# ----------------------------------------------------------------------------------------
+# Canvas
+# ----------------------------------------------------------------------------------------
+
+proc newCanvas(control: Control = nil): CanvasImpl =
+ result = new CanvasImpl
+ result.fLineColor = rgb(0, 0, 0)
+ result.fAreaColor = rgb(0, 0, 0)
+ if control == nil:
+ result.fFontFamily = app.defaultFontFamily
+ result.fFontSize = app.defaultFontSize
+ result.fTextColor = app.defaultTextColor
+ else:
+ result.fFontFamily = control.fontFamily
+ result.fFontSize = control.fontSize
+ result.fTextColor = control.textColor
+ result.fWidth = control.width
+ result.fHeight = control.height
+ control.fCanvas = result
+
+method destroy(canvas: Canvas) = discard
+
+method width(canvas: Canvas): int = canvas.fWidth
+
+method height(canvas: Canvas): int= canvas.fHeight
+
+method fontFamily(canvas: Canvas): string = canvas.fFontFamily
+
+method `fontFamily=`(canvas: Canvas, fontFamily: string) = canvas.fFontFamily = fontFamily
+
+method fontSize(canvas: Canvas): int = canvas.fFontSize
+
+method `fontSize=`(canvas: Canvas, fontSize: int) = canvas.fFontSize = fontSize
+
+method textColor(canvas: Canvas): Color = canvas.fTextColor
+
+method `textColor=`(canvas: Canvas, color: Color) = canvas.fTextColor = color
+
+method lineColor(canvas: Canvas): Color = canvas.fLineColor
+
+method `lineColor=`(canvas: Canvas, color: Color) = canvas.fLineColor = color
+
+method areaColor(canvas: Canvas): Color = canvas.fAreaColor
+
+method `areaColor=`(canvas: Canvas, color: Color) = canvas.fAreaColor = color
+
+method getTextLineWidth(canvas: Canvas, text: string): int =
+ result = text.len * 7
+ # should be overrriden by CanvasImpl
+
+method getTextLineHeight(canvas: Canvas): int =
+ result = 20
+ # should be overrriden by CanvasImpl
+
+method getTextWidth(canvas: Canvas, text: string): int =
+ result = 0
+ for line in text.splitLines:
+ result = max(result, canvas.getTextLineWidth(line))
+
+method drawTextCentered(canvas: Canvas, text: string, x, y = 0, width, height = -1) =
+ var w = width
+ if w == -1:
+ w = canvas.width
+ var h = height
+ if h == -1:
+ h = canvas.height
+ let rx = x + (w - canvas.getTextWidth(text)) div 2
+ let ry = y + (h - canvas.getTextLineHeight()) div 2
+ canvas.drawText(text, rx, ry)
+
+method fill(canvas: Canvas) = canvas.drawRectArea(0, 0, canvas.width, canvas.height)
+
+
+# ----------------------------------------------------------------------------------------
+# Image
+# ----------------------------------------------------------------------------------------
+
+proc newImage(): Image =
+ result = new ImageImpl
+ result.fCanvas = newCanvas()
+
+method width(image: Image): int = image.canvas.width
+
+method height(image: Image): int = image.canvas.height
+
+method canvas(image: Image): Canvas = image.fCanvas
+
+
+# ----------------------------------------------------------------------------------------
+# Window
+# ----------------------------------------------------------------------------------------
+
+proc newWindow(title: string = nil): Window =
+ result = new WindowImpl
+ result.WindowImpl.init()
+ if title != nil:
+ result.title = title
+
+
+proc init(window: Window) =
+ window.fVisible = false
+ window.fWidth = 640 # do not trigger resize
+ window.height = 480 # trigger resize
+ window.fX = -1 # window will be centered on screen
+ window.fY = -1
+ window.title = getAppFilename().extractFilename().changeFileExt("")
+ var defaultIconPath = getAppFilename().changeFileExt("") & ".png"
+ if defaultIconPath.fileExists():
+ window.iconPath = defaultIconPath
+ windowList.add(window)
+ window.triggerRelayout()
+
+
+method destroy(window: Window) =
+ if window.fControl != nil:
+ window.fControl.destroy()
+ # should be extended by WindowImpl
+
+proc disposeInner(window: Window): bool =
+ var event = new WindowDisposeEvent
+ event.window = window
+ window.handleDisposeEvent(event)
+ if event.cancel:
+ return false
+ window.destroy()
+ let i = windowList.find(window)
+ windowList.delete(i)
+ if quitOnLastWindowClose and windowList.len == 0:
+ quit()
+ window.fDisposed = true
+ return true
+
+proc dispose(window: var Window) =
+ if window.disposeInner():
+ window = nil
+
+proc dispose(window: Window) =
+ discard window.disposeInner()
+
+proc disposed(window: Window): bool = window == nil or window.fDisposed
+
+method title(window: Window): string = window.fTitle
+
+method `title=`(window: Window, title: string) = window.fTitle = title
+
+method control(window: Window): Control = window.fControl
+
+method `control=`(window: Window, control: Control) =
+ window.fControl = control
+ control.fParentWindow = window
+ # should be extended by WindowImpl
+
+method add(window: Window, control: Control) =
+ if window.control != nil:
+ raiseError("Window can have only one control.")
+ window.control = control
+
+method visible(window: Window): bool = window.fVisible
+
+method `visible=`(window: Window, visible: bool) =
+ window.fVisible = visible
+ if window.x == -1 or window.y == -1:
+ window.centerOnScreen()
+
+method show(window: Window) = window.visible = true
+
+method showModal(window: Window, parent: Window) =
+ window.visible = true
+ # should be extended by WindowImpl
+
+method hide(window: Window) = window.visible = false
+
+method x(window: Window): int = window.fX
+
+method `x=`(window: Window, x: int) =
+ window.fX = x
+ # should be extended by WindowImpl
+
+method y(window: Window): int = window.fY
+
+method `y=`(window: Window, y: int) =
+ window.fY = y
+ # should be extended by WindowImpl
+
+method centerOnScreen(window: Window) =
+ discard # has to be implemented in WindowImpl
+
+method width(window: Window): int = window.fWidth
+
+method height(window: Window): int = window.fHeight
+
+method `width=`(window: Window, width: int) =
+ window.fWidth = width
+ window.triggerRelayout()
+ var event = new ResizeEvent
+ event.window = window
+ window.handleResizeEvent(event)
+
+method `height=`(window: Window, height: int) =
+ window.fHeight = height
+ window.triggerRelayout()
+ var event = new ResizeEvent
+ event.window = window
+ window.handleResizeEvent(event)
+
+method clientWidth(window: Window): int = window.fClientWidth
+
+method clientHeight(window: Window): int = window.fClientHeight
+
+proc triggerRelayout(window: Window) =
+ if window.control == nil:
+ return
+ # echo ""
+ # echo "WindowImpl:triggerRelayout()"
+ # echo "window size: " & $window.clientWidth & ", " & $window.clientHeight
+ window.control.relayout(window.clientWidth, window.clientHeight)
+
+method iconPath(window: Window): string = window.fIconPath
+
+method `iconPath=`(window: Window, iconPath: string) =
+ window.fIconPath = iconPath
+ # should be extended by WindowImpl
+
+method handleDisposeEvent(window: Window, event: WindowDisposeEvent) =
+ # can be overriden by custom window
+ let callback = window.onDispose
+ if callback != nil:
+ callback(event)
+
+method handleResizeEvent(window: Window, event: ResizeEvent) =
+ # can be overriden by custom window
+ let callback = window.onResize
+ if callback != nil:
+ callback(event)
+
+method handleDropFilesEvent(window: Window, event: DropFilesEvent) =
+ # can be overriden by custom window
+ let callback = window.onDropFiles
+ if callback != nil:
+ callback(event)
+
+method handleKeyDownEvent(window: Window, event: WindowKeyEvent) =
+ # can be overriden by custom window
+ let callback = window.onKeyDown
+ if callback != nil:
+ callback(event)
+
+method onDispose(window: Window): WindowDisposeProc = window.fOnDispose
+method `onDispose=`(window: Window, callback: WindowDisposeProc) = window.fOnDispose = callback
+
+method onResize(window: Window): ResizeProc = window.fOnResize
+method `onResize=`(window: Window, callback: ResizeProc) = window.fOnResize = callback
+
+method onDropFiles(window: Window): DropFilesProc = window.fOnDropFiles
+method `onDropFiles=`(window: Window, callback: DropFilesProc) = window.fOnDropFiles = callback
+
+method onKeyDown(window: Window): WindowKeyProc = window.fOnKeyDown
+method `onKeyDown=`(window: Window, callback: WindowKeyProc) = window.fOnKeyDown = callback
+
+
+
+# ----------------------------------------------------------------------------------------
+# Control
+# ----------------------------------------------------------------------------------------
+
+proc newControl(): Control =
+ result = new ControlImpl
+ result.ControlImpl.init()
+
+proc init(control: Control) =
+ control.tag = ""
+ control.fWidthMode = WidthMode_Expand
+ control.fHeightMode = HeightMode_Expand
+ control.fScrollableWidth = -1
+ control.fScrollableHeight = -1
+ control.resetFontFamily()
+ control.resetFontSize()
+ control.resetTextColor()
+ control.resetBackgroundColor()
+ control.show()
+ # should be extended by WindowImpl
+
+method destroy(control: Control) =
+ discard # nothing to do here
+ # should be extended by WindowImpl
+
+proc dispose(control: var Control) =
+ control.destroy()
+ control = nil
+
+proc dispose(control: Control) =
+ control.destroy()
+ control.fDisposed = true
+
+proc disposed(control: Control): bool = control == nil or control.fDisposed
+
+method visible(control: Control): bool = control.fVisible
+
+method `visible=`(control: Control, visible: bool) =
+ control.fVisible = visible
+ control.triggerRelayout()
+ # should be extended by WindowImpl
+
+method show(control: Control) = control.visible = true
+
+method hide(control: Control) = control.visible = false
+
+method width(control: Control): int = control.fWidth
+
+method height(control: Control): int = control.fHeight
+
+method `width=`(control: Control, width: int) =
+ control.setSize(width, control.fHeight)
+ control.widthMode = WidthMode_Static
+
+method `height=`(control: Control, height: int) =
+ control.setSize(control.fWidth, height)
+ control.heightMode = HeightMode_Static
+
+method minWidth(control: Control): int = control.fMinWidth
+
+method `minWidth=`(control: Control, minWidth: int) =
+ control.fMinWidth = minWidth
+ control.triggerRelayout()
+
+method minHeight(control: Control): int = control.fMinHeight
+
+method `minHeight=`(control: Control, minHeight: int) =
+ control.fMinHeight = minHeight
+ control.triggerRelayout()
+
+method maxWidth(control: Control): int = control.fMaxWidth
+
+method `maxWidth=`(control: Control, maxWidth: int) =
+ control.fMaxWidth = maxWidth
+ control.triggerRelayout()
+
+method maxHeight(control: Control): int = control.fMaxHeight
+
+method `maxHeight=`(control: Control, maxHeight: int) =
+ control.fMaxHeight = maxHeight
+ control.triggerRelayout()
+
+method setSize(control: Control, width, height: int) =
+ control.fWidth = width
+ control.fHeight = height
+ if control.canvas != nil:
+ control.canvas.fWidth = width
+ control.canvas.fHeight = height
+ control.realignChildControls()
+ # should be extended by ControlImpl
+
+method setSize(control: ControlImpl, width, height: int) # required pre declaration
+
+method `x=`(control: Control, x: int) =
+ if control.fParentControl == nil:
+ raiseError("Control cannot be moved, when it is not inside a container.")
+ cast[Container](control.fParentControl).setControlPosition(control, x, control.y)
+
+method x(control: Control): int = control.fX
+
+method `y=`(control: Control, y: int) =
+ if control.fParentControl == nil:
+ raiseError("Control cannot be moved, when it is not inside a container.")
+ cast[Container](control.fParentControl).setControlPosition(control, control.x, y)
+
+method y(control: Control): int = control.fY
+
+method setPosition(control: Control, x, y: int) =
+ control.fX = x
+ control.fY = y
+ # should be extended by ControlImpl
+
+method naturalWidth(control: Control): int = control.width
+
+method naturalHeight(control: Control): int = control.height
+
+method wantedWidth(control: Control): int =
+ if control.widthMode == WidthMode_Static:
+ result = control.width
+ else:
+ result = control.naturalWidth
+ if result != -1 and control.minWidth > result:
+ result = control.minWidth
+ if control.maxWidth > 0 and control.maxWidth < result:
+ result = control.maxWidth
+
+method wantedHeight(control: Control): int =
+ if control.heightMode == HeightMode_Static:
+ result = control.height
+ else:
+ result = control.naturalHeight
+ if result != -1 and control.minHeight > result:
+ result = control.minHeight
+ if control.maxHeight > 0 and control.maxHeight < result:
+ result = control.maxHeight
+
+method `widthMode=`(control: Control, mode: WidthMode) =
+ control.fWidthMode = mode
+ control.triggerRelayout()
+
+method `heightMode=`(control: Control, mode: HeightMode) =
+ control.fHeightMode = mode
+ control.triggerRelayout()
+
+method widthMode(control: Control): WidthMode = control.fWidthMode
+
+method heightMode(control: Control): HeightMode = control.fHeightMode
+
+method childControls(control: Control): seq[Control] = @[]
+
+method parentControl(control: Control): Control = control.fParentControl
+
+method parentWindow(control: Control): WindowImpl =
+ if control.fParentControl == nil:
+ result = cast[WindowImpl](control.fParentWindow)
+ else:
+ result = control.parentControl.parentWindow
+
+proc triggerRelayout(control: Control) =
+ var con = control
+ while con.parentControl != nil:
+ con = con.parentControl
+ if con.parentWindow != nil:
+ con.parentWindow.triggerRelayout()
+ if control.parentControl != nil:
+ control.parentControl.realignChildControls()
+ control.realignChildControls()
+
+proc triggerRelayoutIfModeIsAuto(control: Control) =
+ if control.widthMode == WidthMode_Auto or control.heightMode == HeightMode_Auto:
+ control.triggerRelayout()
+
+method relayout(control: Control, availableWidth, availableHeight: int) =
+ # echo ""
+ # echo control.tag & ".relayout(): "
+ # echo " available size: " & $availableWidth & ", " & $availableHeight
+ var width = control.width
+ var height = control.height
+ var sizeChanged = false
+ if control.widthMode == WidthMode_Auto:
+ let naturalWidth = control.naturalWidth
+ # echo " naturalWidth: " & $naturalWidth
+ if naturalWidth == -1:
+ width = availableWidth
+ else:
+ width = min(availableWidth, naturalWidth)
+ sizeChanged = true
+ elif control.widthMode in {WidthMode_Expand, WidthMode_Fill}:
+ width = availableWidth
+ sizeChanged = true
+ if control.heightMode == HeightMode_Auto:
+ let naturalHeight = control.naturalHeight
+ # echo " naturalHeight: " & $naturalHeight
+ if naturalHeight == -1:
+ height = availableHeight
+ else:
+ height = min(availableHeight, naturalHeight)
+ sizeChanged = true
+ elif control.heightMode in {HeightMode_Expand, HeightMode_Fill}:
+ height = availableHeight
+ sizeChanged = true
+ if sizeChanged:
+ # echo " new size: " & $width & ", " & $height
+ control.setSize(width, height)
+
+method realignChildControls(control: Control) = discard
+
+method focus(control: Control) =
+ discard
+ # should be overrriden by ControlImpl
+
+method getTextLineWidth(control: Control, text: string): int =
+ result = text.len * 7
+ # should be overrriden by ControlImpl
+
+method getTextLineHeight(control: Control): int =
+ result = 20
+ # should be overrriden by ControlImpl
+
+method getTextWidth(control: Control, text: string): int =
+ result = 0
+ for line in text.splitLines:
+ result = max(result, control.getTextLineWidth(line))
+
+method visibleWidth(control: Control): int =
+ result = control.width
+ if control.fXScrollEnabled:
+ result.dec(fScrollbarSize)
+
+method visibleHeight(control: Control): int =
+ result = control.height
+ if control.fYScrollEnabled:
+ result.dec(fScrollbarSize)
+
+method xScrollPos(control: Control): int = control.fXScrollPos
+
+method `xScrollPos=`(control: Control, xScrollPos: int) =
+ control.fXScrollPos = xScrollPos
+
+method yScrollPos(control: Control): int = control.fYScrollPos
+
+method `yScrollPos=`(control: Control, yScrollPos: int) =
+ control.fYScrollPos = yScrollPos
+
+method scrollableWidth(control: Control): int = control.fScrollableWidth
+
+method `scrollableWidth=`(control: Control, scrollableWidth: int) =
+ control.fScrollableWidth = scrollableWidth
+
+method scrollableHeight(control: Control): int = control.fScrollableHeight
+
+method `scrollableHeight=`(control: Control, scrollableHeight: int) =
+ control.fScrollableHeight = scrollableHeight
+
+method fontFamily(control: Control): string = control.fFontFamily
+
+method `fontFamily=`(control: Control, fontFamily: string) =
+ control.setFontFamily(fontFamily)
+ control.fUseDefaultFontFamily = false
+
+method setFontFamily(control: Control, fontFamily: string) =
+ control.fFontFamily = fontFamily
+ control.triggerRelayoutIfModeIsAuto()
+ # should be extended by ControlImpl
+
+method resetFontFamily(control: Control) =
+ control.setFontFamily(fDefaultFontFamily)
+ control.fUseDefaultFontFamily = true
+
+method fontSize(control: Control): int = control.fFontSize
+
+method `fontSize=`(control: Control, fontSize: int) =
+ control.setFontSize(fontSize)
+ control.fUseDefaultFontSize = false
+
+method setFontSize(control: Control, fontSize: int) =
+ control.fFontSize = fontSize
+ control.triggerRelayoutIfModeIsAuto()
+ # should be extended by ControlImpl
+
+method resetFontSize(control: Control) =
+ control.setFontSize(fDefaultFontSize)
+ control.fUseDefaultFontSize = true
+
+method backgroundColor(control: Control): Color = control.fBackgroundColor
+
+method `backgroundColor=`(control: Control, color: Color) =
+ control.setBackgroundColor(color)
+ control.fUseDefaultBackgroundColor = false
+
+method setBackgroundColor(control: Control, color: Color) =
+ control.fBackgroundColor = color
+ control.forceRedraw()
+ # should be extended by ControlImpl
+
+method resetBackgroundColor(control: Control) =
+ control.setBackgroundColor(fDefaultBackgroundColor)
+ control.fUseDefaultBackgroundColor = true
+
+method textColor(control: Control): Color = control.fTextColor
+
+method `textColor=`(control: Control, color: Color) =
+ control.setTextColor(color)
+ control.fUseDefaultTextColor = false
+
+method setTextColor(control: Control, color: Color) =
+ control.fTextColor = color
+ control.forceRedraw()
+ # should be extended by ControlImpl
+
+method resetTextColor*(control: Control) =
+ control.setTextColor(fDefaultTextColor)
+ control.fUseDefaultTextColor = true
+
+method forceRedraw(control: Control) =
+ discard
+ # should be implemented by ControlImpl
+
+method canvas(control: Control): Canvas = control.fCanvas
+
+method handleDisposeEvent(control: Control, event: ControlDisposeEvent) =
+ # can be overriden by custom window
+ let callback = control.onDispose
+ if callback != nil:
+ callback(event)
+
+method handleDrawEvent(control: Control, event: DrawEvent) =
+ # can be implemented by custom control
+ let callback = control.onDraw
+ if callback != nil:
+ callback(event)
+
+method handleMouseButtonDownEvent(control: Control, event: MouseButtonEvent) =
+ # can be implemented by custom control
+ let callback = control.onMouseButtonDown
+ if callback != nil:
+ callback(event)
+
+method handleMouseButtonUpEvent(control: Control, event: MouseButtonEvent) =
+ # can be implemented by custom control
+ let callback = control.onMouseButtonUp
+ if callback != nil:
+ callback(event)
+
+method handleClickEvent(control: Control, event: ClickEvent) =
+ # can be overridden by custom button
+ let callback = control.onClick
+ if callback != nil:
+ callback(event)
+
+method handleKeyDownEvent(control: Control, event: ControlKeyEvent) =
+ # can be implemented by custom control
+ let callback = control.onKeyDown
+ if callback != nil:
+ callback(event)
+
+method handleTextChangeEvent(control: Control, event: TextChangeEvent) =
+ # can be implemented by custom control
+ let callback = control.onTextChange
+ if callback != nil:
+ callback(event)
+
+method onDispose(control: Control): ControlDisposeProc = control.fOnDispose
+method `onDispose=`(control: Control, callback: ControlDisposeProc) = control.fOnDispose = callback
+
+method onDraw(control: Control): DrawProc = control.fOnDraw
+method `onDraw=`(control: Control, callback: DrawProc) = control.fOnDraw = callback
+
+method onMouseButtonDown(control: Control): MouseButtonProc = control.fOnMouseButtonDown
+method `onMouseButtonDown=`(control: Control, callback: MouseButtonProc) = control.fOnMouseButtonDown = callback
+
+method onMouseButtonUp(control: Control): MouseButtonProc = control.fOnMouseButtonUp
+method `onMouseButtonUp=`(control: Control, callback: MouseButtonProc) = control.fOnMouseButtonUp = callback
+
+method onClick(control: Control): ClickProc = control.fOnClick
+method `onClick=`(control: Control, callback: ClickProc) = control.fOnClick = callback
+
+method onKeyDown(control: Control): ControlKeyProc = control.fOnKeyDown
+method `onKeyDown=`(control: Control, callback: ControlKeyProc) = control.fOnKeyDown = callback
+
+method onTextChange(control: Control): TextChangeProc = control.fOnTextChange
+method `onTextChange=`(control: Control, callback: TextChangeProc) = control.fOnTextChange = callback
+
+
+# ----------------------------------------------------------------------------------------
+# Container
+# ----------------------------------------------------------------------------------------
+
+proc newContainer(): Container =
+ result = new ContainerImpl
+ result.ContainerImpl.init()
+
+proc init(container: Container) =
+ container.fChildControls = @[]
+ container.ControlImpl.init()
+ container.fWidthMode = WidthMode_Auto
+ container.fHeightMode = HeightMode_Auto
+
+method frame(container: Container): Frame = container.fFrame
+
+method `frame=`(container: Container, frame: Frame) =
+ if frame.fParentControl != nil:
+ raiseError("Frame can be assigned only to one container.")
+ container.fFrame = frame
+ if frame != nil:
+ frame.fParentControl = container
+ container.tag = frame.tag
+ container.triggerRelayout()
+ if container.frame != nil:
+ container.frame.setSize(container.width, container.height)
+ # should be extended by NativeFrame
+
+method setSize(container: Container, width, height: int) =
+ procCall container.ControlImpl.setSize(width, height)
+ if container.frame != nil:
+ container.frame.setSize(width, height)
+
+method childControls(container: Container): seq[Control] = container.fChildControls
+
+method add(container: Container, control: Control) =
+ if control.fParentControl != nil:
+ raiseError("Control can be added only to one container.")
+ container.fChildControls.add(control)
+ control.fParentControl = container
+ control.fIndex = 0
+ container.triggerRelayout()
+
+method remove(container: Container, control: Control) =
+ discard
+ # if container != control.fParentControl:
+ # raiseError("control can not be removed because it is not member of the container")
+ # else:
+ # let startIndex = control.fIndex
+ # container.childControls.del(control.fIndex)
+ # for i in startIndex..container.childControls.high:
+ # container.childControl[i].fIndex = i
+ # control.parentControl = nil
+
+method setControlPosition(container: Container, control: Control, x, y: int) =
+ control.setPosition(x, y)
+ container.triggerRelayout()
+
+method minWidth(container: Container): int =
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ result = max(result, control.x + control.minWidth)
+ let padding = container.getPadding()
+ result.inc(padding.left)
+ result.inc(padding.right)
+ result = max(result, container.fMinWidth)
+
+method minHeight(container: Container): int =
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ result = max(result, control.y + control.minHeight)
+ let padding = container.getPadding()
+ result.inc(padding.top)
+ result.inc(padding.bottom)
+ result = max(result, container.fMinHeight)
+
+method naturalWidth(container: Container): int =
+ if container.widthMode == WidthMode_Static:
+ return container.width
+ if container.widthMode == WidthMode_Expand:
+ return -1
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ if control.widthMode == WidthMode_Expand:
+ return -1
+ result = max(result, control.x + control.wantedWidth)
+ let padding = container.getPadding()
+ result.inc(padding.left)
+ result.inc(padding.right)
+ if container.frame != nil and container.frame.visible:
+ result = max(result, container.frame.naturalWidth)
+
+method naturalHeight(container: Container): int =
+ if container.heightMode == HeightMode_Static:
+ return container.height
+ if container.heightMode == HeightMode_Expand:
+ return -1
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ if control.heightMode == HeightMode_Expand:
+ return -1
+ result = max(result, control.y + control.wantedHeight)
+ let padding = container.getPadding()
+ result.inc(padding.top)
+ result.inc(padding.bottom)
+
+method getPadding(container: Container): Spacing =
+ if container.frame != nil and container.frame.visible:
+ result = container.frame.getPadding()
+
+method setInnerSize(container: Container, width, height: int) = discard
+ # should be extended by ContainerImpl
+
+method updateInnerSize(container: Container, pInnerWidth, pInnerHeight: int) {.base.} =
+ let padding = container.getPadding()
+ let clientWidth = container.width - padding.left - padding.right
+ let clientHeight = container.height - padding.top - padding.bottom
+ var innerWidth = pInnerWidth
+ var innerHeight = pInnerHeight
+
+ discard """ container.xScrollEnabled = innerWidth > clientWidth
+
+ if container.xScrollEnabled and innerHeight + 20 > clientHeight:
+ innerHeight.inc(20) # Space for scrollbar
+ container.yScrollEnabled = true
+ else:
+ container.yScrollEnabled = innerHeight > clientHeight
+ if container.yScrollEnabled and innerWidth + 20 > clientWidth:
+ innerWidth.inc(20) # Space for scrollbar
+ container.xScrollEnabled = true """
+
+ # TODO: rework
+
+ innerWidth = max(innerWidth, clientWidth)
+ innerHeight = max(innerHeight, clientHeight)
+
+ container.scrollableWidth = innerWidth
+ container.scrollableHeight = innerHeight
+ container.setInnerSize(innerWidth, innerHeight)
+
+method realignChildControls(container: Container) =
+ let padding = container.getPadding()
+ var innerWidth = container.wantedWidth
+ var innerHeight = container.wantedHeight
+ if innerWidth == -1:
+ innerWidth = container.width
+ if innerHeight == -1:
+ innerHeight = container.height
+ container.updateInnerSize(innerWidth - padding.left - padding.right, innerHeight - padding.top - padding.bottom)
+
+method `onDraw=`(container: ContainerImpl, callback: DrawProc) = raiseError("ContainerImpl does not allow onDraw.")
+
+
+# ----------------------------------------------------------------------------------------
+# LayoutContainer
+# ----------------------------------------------------------------------------------------
+
+proc newLayoutContainer(layout: Layout): LayoutContainer =
+ result = new LayoutContainer
+ result.init()
+ result.layout = layout
+ result.xAlign = XAlign_Left
+ result.yAlign = YAlign_Top
+ result.spacing = 4
+ result.padding = 2
+
+method naturalWidth(container: LayoutContainer): int =
+ # echo container.tag & ".naturalWidth"
+ if container.widthMode == WidthMode_Static:
+ return container.width
+ if container.widthMode == WidthMode_Expand:
+ return -1
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ if control.widthMode == WidthMode_Expand or control.wantedWidth == -1:
+ return -1
+ if container.layout == Layout_Horizontal:
+ result.inc(control.wantedWidth)
+ else:
+ result = max(result, control.wantedWidth)
+ let padding = container.getPadding()
+ result.inc(padding.left)
+ result.inc(padding.right)
+ result.inc(container.padding * 2)
+ if container.layout == Layout_Horizontal and container.childControls.len > 1:
+ result.inc(container.spacing * (container.childControls.len - 1))
+ if container.frame != nil and container.frame.visible:
+ result = max(result, container.frame.naturalWidth)
+
+method naturalHeight(container: LayoutContainer): int =
+ if container.heightMode == HeightMode_Static:
+ return container.height
+ if container.heightMode == HeightMode_Expand:
+ return -1
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ if control.heightMode == HeightMode_Expand or control.wantedHeight == -1:
+ return -1
+ if container.layout == Layout_Vertical:
+ result.inc(control.wantedHeight)
+ else:
+ result = max(result, control.wantedHeight)
+ let padding = container.getPadding()
+ result.inc(padding.top)
+ result.inc(padding.bottom)
+ result.inc(container.padding * 2)
+ if container.layout == Layout_Vertical and container.childControls.len > 1:
+ result.inc(container.spacing * (container.childControls.len - 1))
+
+method minWidth(container: LayoutContainer): int =
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ if container.layout == Layout_Horizontal:
+ result.inc(control.minWidth)
+ else:
+ result = max(result, control.minWidth)
+ let padding = container.getPadding()
+ result.inc(padding.left)
+ result.inc(padding.right)
+ result.inc(container.padding * 2)
+ if container.layout == Layout_Horizontal and container.childControls.len > 1:
+ result.inc(container.spacing * (container.childControls.len - 1))
+ result = max(result, container.fMinWidth)
+
+method minHeight(container: LayoutContainer): int =
+ result = 0
+ for control in container.childControls:
+ if not control.visible:
+ continue
+ if container.layout == Layout_Vertical:
+ result.inc(control.minHeight)
+ else:
+ result = max(result, control.minHeight)
+ let padding = container.getPadding()
+ result.inc(padding.top)
+ result.inc(padding.bottom)
+ result.inc(container.padding * 2)
+ if container.layout == Layout_Vertical and container.childControls.len > 1:
+ result.inc(container.spacing * (container.childControls.len - 1))
+ result = max(result, container.fMinHeight)
+
+method setControlPosition(container: LayoutContainer, control: Control, x, y: int) =
+ raiseError("Controls inside a LayoutContainer cannot be moved manually.")
+
+method layout(container: LayoutContainer): Layout = container.fLayout
+
+method `layout=`(container: LayoutContainer, layout: Layout) =
+ container.fLayout = layout
+ container.triggerRelayout()
+
+method xAlign(container: LayoutContainer): XAlign = container.fXAlign
+
+method `xAlign=`(container: LayoutContainer, xAlign: XAlign) =
+ container.fXAlign = xAlign
+ container.realignChildControls()
+
+method yAlign(container: LayoutContainer): YAlign = container.fYAlign
+
+method `yAlign=`(container: LayoutContainer, yAlign: YAlign) =
+ container.fYAlign = yAlign
+ container.realignChildControls()
+
+method padding(container: LayoutContainer): int = container.fPadding
+
+method `padding=`(container: LayoutContainer, padding: int) =
+ container.fPadding = padding
+ container.triggerRelayout()
+
+method spacing(container: LayoutContainer): int = container.fSpacing
+
+method `spacing=`(container: LayoutContainer, spacing: int) =
+ container.fSpacing = spacing
+ container.triggerRelayout()
+
+method realignChildControls(container: LayoutContainer) =
+ # echo ""
+ # echo container.tag & ".realignChildControls()"
+ if container.fChildControls.len == 0:
+ return
+ # echo " container size: " & $container.width & ", " & $container.height
+ let padding = container.getPadding()
+ let clientWidth = container.width - padding.left - padding.right
+ let clientHeight = container.height - padding.top - padding.bottom
+ # echo " client size: " & $clientWidth & ", " & $clientHeight
+ var minInnerWidth = 0
+ var minInnerHeight = 0
+ var expandWidthCount = 0
+ var expandHeightCount = 0
+
+ # Calculate minimum needed size:
+ for control in container.fChildControls:
+ if not control.visible:
+ continue
+
+ if control.widthMode == WidthMode_Expand:
+ if container.layout == Layout_Horizontal:
+ minInnerWidth.inc(control.minWidth)
+ expandWidthCount.inc
+
+ elif control.widthMode in {WidthMode_Auto , WidthMode_Static}:
+ if container.layout == Layout_Horizontal:
+ if control.wantedWidth == -1:
+ minInnerWidth.inc(control.minWidth)
+ expandWidthCount.inc
+ else:
+ minInnerWidth.inc(control.wantedWidth)
+ else:
+ if control.wantedWidth == -1:
+ expandWidthCount.inc
+ else:
+ minInnerWidth = max(minInnerWidth, control.wantedWidth)
+
+ if control.heightMode == HeightMode_Expand:
+ if container.layout == Layout_Vertical:
+ minInnerHeight.inc(control.minHeight)
+ expandHeightCount.inc
+
+ elif control.heightMode in {HeightMode_Auto, HeightMode_Static}:
+ if container.layout == Layout_Vertical:
+ if control.wantedHeight == -1:
+ minInnerHeight.inc(control.minHeight)
+ expandHeightCount.inc
+ else:
+ minInnerHeight.inc(control.wantedHeight)
+ else:
+ if control.wantedHeight == -1:
+ expandHeightCount.inc
+ else:
+ minInnerHeight = max(minInnerHeight, control.wantedHeight)
+
+ # Add padding:
+ minInnerWidth.inc(container.padding * 2)
+ minInnerHeight.inc(container.padding * 2)
+
+ # Add spacing:
+ if container.childControls.len > 1:
+ if container.layout == Layout_Horizontal:
+ minInnerWidth.inc(container.spacing * (container.childControls.len - 1))
+ if container.layout == Layout_Vertical:
+ minInnerHeight.inc(container.spacing * (container.childControls.len - 1))
+
+ container.updateInnerSize(minInnerWidth, minInnerHeight)
+
+ let innerWidth = max(minInnerWidth, clientWidth)
+ let innerHeight = max(minInnerHeight, clientHeight)
+
+ # Calculate dynamic size:
+ var dynamicWidth = clientWidth - minInnerWidth
+ var dynamicHeight = clientHeight - minInnerHeight
+ dynamicWidth = max(dynamicWidth, 0)
+ dynamicHeight = max(dynamicHeight, 0)
+
+ # Move and resize controls:
+ var x = container.padding
+ var y = container.padding
+
+ if (container.xAlign == XAlign_Center or (container.xAlign == XAlign_Spread and container.childControls.len == 1)) and (container.layout == Layout_Vertical or expandWidthCount == 0):
+ x.inc(dynamicWidth div 2)
+ if (container.yAlign == YAlign_Center or (container.yAlign == YAlign_Spread and container.childControls.len == 1)) and (container.layout == Layout_Horizontal or expandHeightCount == 0):
+ y.inc(dynamicHeight div 2)
+
+ for control in container.fChildControls:
+ if not control.visible:
+ continue
+ # echo " child: " & control.tag
+
+ # Size:
+ var width = control.width
+ var height = control.height
+ # echo "size old: " & $width & ", " $height
+ if control.widthMode == WidthMode_Expand or control.wantedWidth == -1:
+ if container.layout == Layout_Horizontal:
+ if expandWidthCount > 0:
+ width = control.minWidth + dynamicWidth div expandWidthCount
+ else:
+ width = control.minWidth + dynamicWidth
+ else:
+ width = clientWidth - container.padding * 2
+ elif control.widthMode == WidthMode_Auto:
+ width = control.wantedWidth
+ elif control.widthMode == WidthMode_Fill:
+ width = clientWidth - container.padding * 2
+
+ if control.minWidth > width:
+ width = control.minWidth
+ if control.maxWidth > 0 and control.maxWidth < width:
+ width = control.maxWidth
+
+ if control.heightMode == HeightMode_Expand or control.wantedHeight == -1:
+ if container.layout == Layout_Vertical:
+ if expandHeightCount > 0:
+ height = control.minHeight + dynamicHeight div expandHeightCount
+ else:
+ height = control.minHeight + dynamicHeight
+ else:
+ height = clientHeight - container.padding * 2
+ elif control.heightMode == HeightMode_Auto:
+ height = control.wantedHeight
+ elif control.heightMode == HeightMode_Fill:
+ height = clientHeight - container.padding * 2
+
+ if control.minHeight > height:
+ height = control.minHeight
+ if control.maxHeight > 0 and control.maxHeight < height:
+ height = control.maxHeight
+
+ if control.width != width or control.height != height:
+ # echo " child: " & control.tag
+ # echo " new size: " & $width & ", " & $height
+ control.setSize(width, height)
+
+ # Position:
+ if container.layout == Layout_Vertical or container.childControls.len == 1:
+ if container.xAlign == XAlign_Center:
+ x = (innerWidth - width) div 2
+ elif container.xAlign == XAlign_Right:
+ x = innerWidth - width - container.padding
+ if container.layout == Layout_Horizontal or container.childControls.len == 1:
+ if container.yAlign == YAlign_Center:
+ y = (innerHeight - height) div 2
+ elif container.yAlign == YAlign_Bottom:
+ y = innerHeight - height - container.padding
+
+ if control.x != x or control.y != y:
+ # echo " new pos: " & $x & ", " $y
+ control.setPosition(x, y)
+
+ # Calculate next position:
+ case container.layout
+ of Layout_Horizontal:
+ x.inc(width)
+ x.inc(container.spacing)
+ if container.xAlign == XAlign_Spread and expandWidthCount == 0 and container.childControls.len > 1:
+ x.inc(dynamicWidth div (container.childControls.len - 1))
+ of Layout_Vertical:
+ y.inc(height)
+ y.inc(container.spacing)
+ if container.yAlign == YAlign_Spread and expandHeightCount == 0 and container.childControls.len > 1:
+ y.inc(dynamicHeight div (container.childControls.len - 1))
+
+
+# ----------------------------------------------------------------------------------------
+# Frame
+# ----------------------------------------------------------------------------------------
+
+proc newFrame(text = ""): Frame =
+ result = new NativeFrame
+ result.NativeFrame.init()
+ result.text = text
+
+proc init(frame: Frame) =
+ frame.ControlImpl.init()
+ frame.fText = ""
+
+method text(frame: Frame): string = frame.fText
+
+method `text=`(frame: Frame, text: string) =
+ frame.fText = text
+ frame.tag = text
+ # should be extended by NativeFrame
+
+method getPadding(frame: Frame): Spacing =
+ result.left = 4
+ result.right = 4
+ result.top = 4
+ result.bottom = 4
+ # should be extended by NativeFrame
+
+method `onDraw=`(container: NativeFrame, callback: DrawProc) = raiseError("NativeFrame does not allow onDraw.")
+
+
+# ----------------------------------------------------------------------------------------
+# Button
+# ----------------------------------------------------------------------------------------
+
+proc newButton(text = ""): Button =
+ result = new NativeButton
+ result.NativeButton.init()
+ result.text = text
+
+proc init(button: Button) =
+ button.ControlImpl.init()
+ button.fText = ""
+ button.fOnClick = nil
+ button.fWidthMode = WidthMode_Auto
+ button.fHeightMode = HeightMode_Auto
+ button.minWidth = 15
+ button.minHeight = 15
+
+method text(button: Button): string = button.fText
+
+method `text=`(button: Button, text: string) =
+ button.fText = text
+ button.tag = text
+ button.triggerRelayoutIfModeIsAuto()
+ # should be extended by NativeButton
+
+method naturalWidth(button: Button): int = button.getTextWidth(button.text) + 20
+
+method naturalHeight(button: Button): int = button.getTextLineHeight() * button.text.countLines + 12
+
+method `onDraw=`(container: NativeButton, callback: DrawProc) = raiseError("NativeButton does not allow onDraw.")
+
+
+# ----------------------------------------------------------------------------------------
+# Label
+# ----------------------------------------------------------------------------------------
+
+proc newLabel(text = ""): Label =
+ result = new NativeLabel
+ result.NativeLabel.init()
+ result.text = text
+
+proc init(label: Label) =
+ label.ControlImpl.init()
+ label.fText = ""
+ label.fWidthMode = WidthMode_Auto
+ label.fHeightMode = HeightMode_Auto
+ label.minWidth = 10
+ label.minHeight = 10
+
+method text(label: Label): string = label.fText
+
+method `text=`(label: Label, text: string) =
+ label.fText = text
+ label.tag = text
+ label.triggerRelayoutIfModeIsAuto()
+
+method naturalWidth(label: Label): int = label.getTextWidth(label.text)
+
+method naturalHeight(label: Label): int = label.getTextLineHeight() * label.text.countLines
+
+method `onDraw=`(container: NativeLabel, callback: DrawProc) = raiseError("NativeLabel does not allow onDraw.")
+
+
+# ----------------------------------------------------------------------------------------
+# TextBox
+# ----------------------------------------------------------------------------------------
+
+proc newTextBox(text = ""): TextBox =
+ result = new NativeTextBox
+ result.NativeTextBox.init()
+ result.text = text
+
+proc init(textBox: TextBox) =
+ textBox.ControlImpl.init()
+ textBox.fWidthMode = WidthMode_Expand
+ textBox.fHeightMode = HeightMode_Auto
+ textBox.minWidth = 20
+ textBox.minHeight = 20
+
+method naturalHeight(textBox: TextBox): int = textBox.getTextLineHeight()
+
+method text(textBox: TextBox): string = discard
+ # has to be implemented by NativeTextBox
+
+method `text=`(textBox: TextBox, text: string) = discard
+ # has to be implemented by NativeTextBox
+
+method `onDraw=`(container: NativeTextBox, callback: DrawProc) = raiseError("NativeTextBox does not allow onDraw.")
+
+
+# ----------------------------------------------------------------------------------------
+# TextArea
+# ----------------------------------------------------------------------------------------
+
+proc newTextArea(text = ""): TextArea =
+ result = new NativeTextArea
+ result.NativeTextArea.init()
+ result.text = text
+
+proc init(textArea: TextArea) =
+ textArea.ControlImpl.init()
+ textArea.fWidthMode = WidthMode_Expand
+ textArea.fHeightMode = HeightMode_Expand
+ textArea.minWidth = 20
+ textArea.minHeight = 20
+ textArea.wrap = true
+
+method text(textArea: TextArea): string = discard
+ # has to be implemented by NativeTextBox
+
+method `text=`(textArea: TextArea, text: string) = discard
+ # has to be implemented by NativeTextBox
+
+method addText(textArea: TextArea, text: string) = textArea.text = textArea.text & text
+
+method addLine(textArea: TextArea, text = "") = textArea.addtext(text & "\n")
+
+method scrollToBottom(textArea: TextArea) = discard
+ # has to be implemented by NativeTextBox
+
+method `onDraw=`(container: NativeTextArea, callback: DrawProc) = raiseError("NativeTextArea does not allow onDraw.")
+
+method wrap(textArea: TextArea): bool = textArea.fWrap
+
+method `wrap=`(textArea: TextArea, wrap: bool) =
+ textArea.fWrap = wrap
+ # should be extended by NativeTextArea
+
+
+# ----------------------------------------------------------------------------------------
+# Platform-specific implementation
+# ----------------------------------------------------------------------------------------
+
+include nigui_platform_impl
diff --git a/src/gtk3/gtk3.nim b/src/gtk3/gtk3.nim
new file mode 100755
index 0000000..f2c7eba
--- /dev/null
+++ b/src/gtk3/gtk3.nim
@@ -0,0 +1,430 @@
+# NiGui - minimal GTK+ 3 binding
+
+{.deadCodeElim: on.}
+
+when defined(windows):
+ const libgtk3Path* = "libgtk-3-0.dll"
+elif defined(gtk_quartz):
+ const libgtk3Path* = "libgtk-3.0.dylib"
+elif defined(macosx):
+ const libgtk3Path* = "libgtk-x11-3.0.dylib"
+else:
+ const libgtk3Path* = "libgtk-3.so(|.0)"
+
+{.pragma: libgtk3, cdecl, dynlib: libgtk3Path.}
+
+# ----------------------------------------------------------------------------------------
+# Types
+# ----------------------------------------------------------------------------------------
+
+type
+ GError* {.byCopy.} = object
+ domain*: int32
+ code*: cint
+ message*: cstring
+
+ GdkRectangle* {.byCopy.} = object
+ x*, y*: cint
+ width*, height*: cint
+
+ GtkBorder* {.byCopy.} = object
+ left*: int16
+ right*: int16
+ top*: int16
+ bottom*: int16
+
+ GdkRGBA* {.byCopy.} = object
+ red*: cdouble
+ green*: cdouble
+ blue*: cdouble
+ alpha*: cdouble
+
+ GList* = ptr object
+ data*: pointer
+ next*: GList
+ prev*: GList
+
+ GtkTargetEntry* {.byCopy.} = object
+ target*: cstring
+ flags*: cint
+ info*: cint
+
+ GdkEventButton* {.byCopy.} = object
+ event_type*: cint
+ window*: pointer
+ send_event*: int8
+ time*: cint
+ x*, y*: cdouble
+ axes*: ptr cdouble
+ state*: cint
+ button*: cint
+ device*: pointer
+ x_root*, y_root*: cdouble
+
+ GdkEventKey* {.byCopy.} = object
+ event_type*: cint
+ window*: pointer
+ send_event*: int8
+ time*: cint
+ state*: cint
+ keyval*: cint
+ length*: cint
+ `string`*: cstring
+ hardware_keycode*: int16
+ group*: int8
+ is_modifier*: int8
+
+ GtkTextIter* {.byCopy.} = object
+ dummy1: pointer
+ dummy2: pointer
+ dummy3: cint
+ dummy4: cint
+ dummy5: cint
+ dummy6: cint
+ dummy7: cint
+ dummy8: cint
+ dummy9: pointer
+ dummy10: pointer
+ dummy11: cint
+ dummy12: cint
+ dummy13: cint
+ dummy14: pointer
+
+# ----------------------------------------------------------------------------------------
+# Constants
+# ----------------------------------------------------------------------------------------
+
+const
+ # GtkWindowType:
+ GTK_WINDOW_TOPLEVEL* = 0
+ GTK_WINDOW_POPUP* = 1
+
+ # GtkDestDefaults:
+ # [..]
+ GTK_DEST_DEFAULT_ALL* = 7
+
+ # GdkDragAction:
+ GDK_ACTION_DEFAULT* = 1
+ GDK_ACTION_COPY* = 2
+ GDK_ACTION_MOVE* = 4
+ GDK_ACTION_LINK* = 8
+ GDK_ACTION_PRIVATE* = 16
+ GDK_ACTION_ASK* = 32
+
+ # GtkOrientation:
+ GTK_ORIENTATION_HORIZONTAL* = 0
+ GTK_ORIENTATION_VERTICAL* = 1
+
+ # GtkWrapMode:
+ GTK_WRAP_NONE* = 0
+ GTK_WRAP_CHAR* = 1
+ GTK_WRAP_WORD* = 2
+ GTK_WRAP_WORD_CHAR* = 3
+
+ # GtkPolicyType:
+ GTK_POLICY_ALWAYS* = 0
+ GTK_POLICY_AUTOMATIC* = 1
+ GTK_POLICY_NEVER* = 2
+ GTK_POLICY_EXTERNAL* = 3
+
+ # PangoEllipsizeMode:
+ PANGO_ELLIPSIZE_NONE* = 0
+ PANGO_ELLIPSIZE_START* = 1
+ PANGO_ELLIPSIZE_MIDDLE* = 2
+ PANGO_ELLIPSIZE_END* = 3
+
+ # GtkButtonBoxStyle:
+ GTK_BUTTONBOX_SPREAD* = 0
+ GTK_BUTTONBOX_EDGE* = 1
+ GTK_BUTTONBOX_START* = 2
+ GTK_BUTTONBOX_END* = 3
+ GTK_BUTTONBOX_CENTER* = 4
+ GTK_BUTTONBOX_EXPAND* = 5
+
+ # GtkJustification:
+ GTK_JUSTIFY_LEFT* = 0
+ GTK_JUSTIFY_RIGHT* = 1
+ GTK_JUSTIFY_CENTER* = 2
+ GTK_JUSTIFY_FILL* = 3
+
+ # GtkStateFlags:
+ GTK_STATE_FLAG_NORMAL* = 0
+ # [..]
+
+ # GdkEventMask:
+ GDK_BUTTON_PRESS_MASK* = 256
+ GDK_BUTTON_RELEASE_MASK* = 512
+ GDK_KEY_PRESS_MASK* = 1024
+ # [..]
+
+ # cairo_format_t:
+ CAIRO_FORMAT_ARGB32* = 0
+ # [..]
+
+ # GtkFileChooserAction:
+ GTK_FILE_CHOOSER_ACTION_OPEN* = 0
+ GTK_FILE_CHOOSER_ACTION_SAVE* = 1
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER* = 2
+ GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER* = 3
+
+ # GtkResponseType:
+ GTK_RESPONSE_NONE* = -1
+ GTK_RESPONSE_REJECT* = -2
+ GTK_RESPONSE_ACCEPT* = -3
+ GTK_RESPONSE_DELETE_EVENT* = -4
+ GTK_RESPONSE_OK* = -5
+ GTK_RESPONSE_CANCEL* = -6
+ GTK_RESPONSE_CLOSE* = -7
+ GTK_RESPONSE_YES* = -8
+ GTK_RESPONSE_NO* = -9
+ GTK_RESPONSE_APPLY* = -10
+ GTK_RESPONSE_HELP* = -11
+
+# ----------------------------------------------------------------------------------------
+# General Gtk Procs
+# ----------------------------------------------------------------------------------------
+
+proc g_slist_length*(list: pointer): int {.importc: "g_slist_length", libgtk3.}
+proc g_slist_nth_data_string*(list: pointer, n: int): cstring {.importc: "g_slist_nth_data", libgtk3.}
+
+proc gtk_init*(argc, argv: pointer) {.importc: "gtk_init", libgtk3.}
+
+proc gtk_main*() {.importc: "gtk_main", libgtk3.}
+proc gtk_main_quit*() {.importc: "gtk_main_quit", libgtk3.}
+proc gtk_events_pending*(): cint {.importc: "gtk_events_pending", libgtk3.}
+proc gtk_main_iteration*(): cint {.importc: "gtk_main_iteration", libgtk3.}
+proc g_timeout_add*(interval: cint, function, data: pointer): cint {.importc: "g_timeout_add", libgtk3.}
+proc g_source_remove*(tag: cint): bool {.importc: "g_source_remove", libgtk3.}
+proc g_signal_connect_data*(instance: pointer, detailed_signal: cstring, c_handler: pointer, data, destroy_data, connect_flags: pointer = nil): pointer {.importc: "g_signal_connect_data", libgtk3.}
+
+proc gtk_window_new*(`type`: cint): pointer {.importc: "gtk_window_new", libgtk3.}
+proc gtk_window_set_title*(window: pointer, title: cstring) {.importc: "gtk_window_set_title", libgtk3.}
+# proc gtk_window_get_title*(window: pointer): cstring {.importc: "gtk_window_get_title", libgtk3.}
+proc gtk_window_set_transient_for*(window, parent: pointer) {.importc: "gtk_window_set_transient_for", libgtk3.}
+proc gtk_window_set_modal*(window: pointer, modal: cint) {.importc: "gtk_window_set_modal", libgtk3.}
+# proc gtk_window_set_default_size*(window: pointer, width, height: cint) {.importc: "gtk_window_set_default_size", libgtk3.}
+proc gtk_window_resize*(window: pointer, width, height: cint) {.importc: "gtk_window_resize", libgtk3.}
+proc gtk_window_resize_to_geometry*(window: pointer, width, height: cint) {.importc: "gtk_window_resize_to_geometry", libgtk3.}
+proc gtk_window_get_size*(window: pointer, width, height: var cint) {.importc: "gtk_window_get_size", libgtk3.}
+proc gtk_window_get_position*(window: pointer, x, y: var cint) {.importc: "gtk_window_get_position", libgtk3.}
+proc gtk_window_move*(window: pointer, x, y: cint) {.importc: "gtk_window_move", libgtk3.}
+proc gtk_window_set_icon_from_file*(window: pointer, filename: cstring, err: pointer) {.importc: "gtk_window_set_icon_from_file", libgtk3.}
+
+proc gdk_window_begin_paint_rect*(window: pointer, rectangle: var GdkRectangle) {.importc: "gdk_window_begin_paint_rect", libgtk3.}
+proc gdk_window_begin_paint_region*(window: pointer, region: pointer) {.importc: "gdk_window_begin_paint_region", libgtk3.}
+proc gdk_window_end_paint*(window: pointer) {.importc: "gdk_window_end_paint", libgtk3.}
+proc gdk_window_get_clip_region*(window: pointer): pointer {.importc: "gdk_window_get_clip_region", libgtk3.}
+
+proc gtk_widget_destroy*(widget: pointer) {.importc: "gtk_widget_destroy", libgtk3.}
+proc gtk_widget_show*(widget: pointer) {.importc: "gtk_widget_show", libgtk3.}
+proc gtk_widget_hide*(widget: pointer) {.importc: "gtk_widget_hide", libgtk3.}
+proc gtk_widget_set_size_request*(widget: pointer, width, height: cint) {.importc: "gtk_widget_set_size_request", libgtk3.}
+proc gtk_widget_size_allocate*(widget: pointer, allocation: var GdkRectangle) {.importc: "gtk_widget_size_allocate", libgtk3.}
+proc gtk_widget_get_size_request*(widget: pointer, width, height: var cint) {.importc: "gtk_widget_get_size_request", libgtk3.}
+proc gtk_widget_get_allocation*(widget: pointer, allocation: var GdkRectangle) {.importc: "gtk_widget_get_allocation", libgtk3.}
+# proc gtk_widget_set_allocation*(widget: pointer, allocation: var GdkRectangle) {.importc: "gtk_widget_set_allocation", libgtk3.}
+# proc gtk_widget_set_hexpand*(widget: pointer, expand: cint) {.importc: "gtk_widget_set_hexpand", libgtk3.}
+proc gtk_widget_queue_draw*(widget: pointer) {.importc: "gtk_widget_queue_draw", libgtk3.}
+proc gtk_widget_set_margin_top*(widget: pointer, margin: cint) {.importc: "gtk_widget_set_margin_top", libgtk3.}
+proc gtk_widget_add_events*(widget: pointer, events: cint) {.importc: "gtk_widget_add_events", libgtk3.}
+proc gtk_widget_set_can_focus*(widget: pointer, can_focus: cint) {.importc: "gtk_widget_set_can_focus", libgtk3.}
+proc gtk_widget_modify_font*(widget: pointer, font_desc: pointer) {.importc: "gtk_widget_modify_font", libgtk3.}
+proc gtk_widget_override_color*(widget: pointer, state: cint, color: var GdkRGBA) {.importc: "gtk_widget_override_color", libgtk3.}
+proc gtk_widget_override_background_color*(widget: pointer, state: cint, color: var GdkRGBA) {.importc: "gtk_widget_override_background_color", libgtk3.}
+proc gtk_widget_get_path*(widget: pointer): pointer {.importc: "gtk_widget_get_path", libgtk3.}
+proc gtk_widget_style_get*(widget: pointer, first_property_name: cstring, value: pointer, passNil: pointer) {.importc: "gtk_widget_style_get", libgtk3.}
+proc gtk_widget_get_style_context*(widget: pointer): pointer {.importc: "gtk_widget_get_style_context", libgtk3.}
+proc gtk_widget_grab_focus*(widget: pointer) {.importc: "gtk_widget_grab_focus", libgtk3.}
+proc gtk_widget_is_focus*(widget: pointer): bool {.importc: "gtk_widget_is_focus", libgtk3.}
+proc gtk_widget_realize*(widget: pointer) {.importc: "gtk_widget_realize", libgtk3.}
+proc gtk_widget_draw*(widget, cr: pointer) {.importc: "gtk_widget_draw", libgtk3.}
+
+proc gtk_container_add*(container, widget: pointer) {.importc: "gtk_container_add", libgtk3.}
+proc gtk_container_remove*(container, widget: pointer) {.importc: "gtk_container_remove", libgtk3.}
+# proc gtk_container_foreach*(container, callback, callback_data: pointer) {.importc: "gtk_container_foreach", libgtk3.}
+proc gtk_container_get_children*(container: pointer): GList {.importc: "gtk_container_get_children", libgtk3.}
+proc gtk_container_set_border_width*(container: pointer, width: cint) {.importc: "gtk_container_set_border_width", libgtk3.}
+
+proc gtk_fixed_new*(): pointer {.importc: "gtk_fixed_new", libgtk3.}
+proc gtk_fixed_move*(fixed, widget: pointer, x, y: cint) {.importc: "gtk_fixed_move", libgtk3.}
+
+proc gtk_layout_new*(hadjustment, vadjustment: pointer): pointer {.importc: "gtk_layout_new", libgtk3.}
+# proc gtk_layout_put*(layout, child_widget: pointer, x, y: cint) {.importc: "gtk_layout_put", libgtk3.}
+proc gtk_layout_move*(layout, child_widget: pointer, x, y: cint) {.importc: "gtk_layout_move", libgtk3.}
+# proc gtk_layout_set_size*(layout: pointer, width, height: cint) {.importc: "gtk_layout_set_size", libgtk3.}
+# proc gtk_layout_get_hadjustment*(layout: pointer): pointer {.importc: "gtk_layout_get_hadjustment", libgtk3.}
+# proc gtk_layout_get_vadjustment*(layout: pointer): pointer {.importc: "gtk_layout_get_vadjustment", libgtk3.}
+
+# proc gtk_scrollbar_new*(orientation: GtkOrientation, adjustment: pointer): pointer {.importc: "gtk_scrollbar_new", libgtk3.}
+
+proc gtk_dialog_new*(): pointer {.importc: "gtk_dialog_new", libgtk3.}
+proc gtk_dialog_run*(dialog: pointer): cint {.importc: "gtk_dialog_run", libgtk3.}
+proc gtk_dialog_add_button*(dialog: pointer, button_text: cstring, response_id: cint): pointer {.importc: "gtk_dialog_add_button", libgtk3.}
+proc gtk_dialog_get_content_area*(dialog: pointer): pointer {.importc: "gtk_dialog_get_content_area", libgtk3.}
+proc gtk_dialog_get_action_area*(dialog: pointer): pointer {.importc: "gtk_dialog_get_action_area", libgtk3.}
+
+proc gtk_file_chooser_dialog_new*(title: string, parent: pointer, action: int, text1: cstring, response1: int, text2: cstring, response2: int, ending: pointer): pointer {.importc: "gtk_file_chooser_dialog_new", libgtk3.}
+proc gtk_file_chooser_set_current_name*(chooser: pointer, name: cstring): bool {.importc: "gtk_file_chooser_set_current_name", libgtk3.}
+proc gtk_file_chooser_get_filename*(chooser: pointer): cstring {.importc: "gtk_file_chooser_get_filename", libgtk3.}
+proc gtk_file_chooser_get_filenames*(chooser: pointer): pointer {.importc: "gtk_file_chooser_get_filenames", libgtk3.}
+proc gtk_file_chooser_set_select_multiple*(chooser: pointer, select_multiple: bool) {.importc: "gtk_file_chooser_set_select_multiple", libgtk3.}
+proc gtk_file_chooser_set_current_folder*(chooser: pointer, filename: cstring): bool {.importc: "gtk_file_chooser_set_current_folder", libgtk3.}
+
+proc gtk_button_box_set_layout*(widget: pointer, layout_style: cint) {.importc: "gtk_button_box_set_layout", libgtk3.}
+
+# proc gtk_message_dialog_new*(parent: pointer, flags: GtkDialogFlags, `type`: GtkMessageType, buttons: GtkButtonsType, message_format: cstring): pointer {.importc: "gtk_message_dialog_new", libgtk3.}
+
+proc gtk_label_new*(str: cstring): pointer {.importc: "gtk_label_new", libgtk3.}
+proc gtk_label_set_text*(label: pointer, str: cstring) {.importc: "gtk_label_set_text", libgtk3.}
+# proc gtk_label_get_text*(label: pointer): cstring {.importc: "gtk_label_get_text", libgtk3.}
+proc gtk_label_set_ellipsize*(label: pointer, mode: cint) {.importc: "gtk_label_set_ellipsize", libgtk3.}
+# proc gtk_label_set_justify*(label: pointer, jtype: cint) {.importc: "gtk_label_set_justify", libgtk3.}
+proc gtk_label_set_xalign*(label: pointer, xalign: cfloat) {.importc: "gtk_label_set_xalign", libgtk3.}
+proc gtk_label_set_yalign*(label: pointer, yalign: cfloat) {.importc: "gtk_label_set_yalign", libgtk3.}
+
+# proc gtk_box_new*(orientation: GtkOrientation, spacing: cint): pointer {.importc: "gtk_box_new", libgtk3.}
+proc gtk_box_pack_start*(box, child: pointer, expand, fill: cint, padding: cint) {.importc: "gtk_box_pack_start", libgtk3.}
+
+proc gtk_button_new*(): pointer {.importc: "gtk_button_new", libgtk3.}
+# proc gtk_button_new_with_label*(label: cstring): pointer {.importc: "gtk_button_new_with_label", libgtk3.}
+# proc gtk_button_get_label*(button: pointer): cstring {.importc: "gtk_button_get_label", libgtk3.}
+proc gtk_button_set_label*(button: pointer, label: cstring) {.importc: "gtk_button_set_label", libgtk3.}
+
+proc gtk_entry_new*(): pointer {.importc: "gtk_entry_new", libgtk3.}
+proc gtk_entry_set_text*(entry: pointer, text: cstring) {.importc: "gtk_entry_set_text", libgtk3.}
+proc gtk_entry_get_text*(entry: pointer): cstring {.importc: "gtk_entry_get_text", libgtk3.}
+proc gtk_entry_set_width_chars*(entry: pointer, n_chars: cint) {.importc: "gtk_entry_set_width_chars", libgtk3.}
+
+proc gtk_text_view_new*(): pointer {.importc: "gtk_text_view_new", libgtk3.}
+proc gtk_text_view_set_buffer*(text_view, buffer: pointer) {.importc: "gtk_text_view_set_buffer", libgtk3.}
+proc gtk_text_view_get_buffer*(text_view: pointer): pointer {.importc: "gtk_text_view_get_buffer", libgtk3.}
+proc gtk_text_view_set_wrap_mode*(text_view: pointer, wrap_mode: cint) {.importc: "gtk_text_view_set_wrap_mode", libgtk3.}
+proc gtk_text_view_set_left_margin*(text_view: pointer, margin: cint) {.importc: "gtk_text_view_set_left_margin", libgtk3.}
+proc gtk_text_view_set_right_margin*(text_view: pointer, margin: cint) {.importc: "gtk_text_view_set_right_margin", libgtk3.}
+proc gtk_text_view_set_top_margin*(text_view: pointer, margin: cint) {.importc: "gtk_text_view_set_top_margin", libgtk3.}
+proc gtk_text_view_set_bottom_margin*(text_view: pointer, margin: cint) {.importc: "gtk_text_view_set_bottom_margin", libgtk3.}
+proc gtk_text_view_scroll_to_iter*(text_view: pointer, iter: var GtkTextIter, within_margin: cfloat, use_align: bool, xalign, yalign: cfloat) {.importc: "gtk_text_view_scroll_to_iter", libgtk3.}
+# proc gtk_text_view_scroll_to_mark*(text_view, mark: pointer, within_margin: cfloat, use_align: bool, xalign, yalign: cfloat) {.importc: "gtk_text_view_scroll_to_mark", libgtk3.}
+# proc gtk_text_view_place_cursor_onscreen*(text_view: pointer): bool {.importc: "gtk_text_view_place_cursor_onscreen", libgtk3.}
+
+# proc gtk_text_mark_new*(name: cstring, left_gravity: bool): pointer {.importc: "gtk_text_mark_new", libgtk3.}
+
+# proc gtk_text_buffer_new*(table: pointer): pointer {.importc: "gtk_text_buffer_new", libgtk3.}
+proc gtk_text_buffer_set_text*(text_buffer: pointer, text: cstring, len: cint) {.importc: "gtk_text_buffer_set_text", libgtk3.}
+proc gtk_text_buffer_get_text*(text_buffer: pointer, start, `end`: var GtkTextIter, include_hidden_chars: cint): cstring {.importc: "gtk_text_buffer_get_text", libgtk3.}
+proc gtk_text_buffer_get_start_iter*(text_buffer: pointer, iter: var GtkTextIter) {.importc: "gtk_text_buffer_get_start_iter", libgtk3.}
+proc gtk_text_buffer_get_end_iter*(text_buffer: pointer, iter: var GtkTextIter) {.importc: "gtk_text_buffer_get_end_iter", libgtk3.}
+# proc gtk_text_buffer_add_mark*(buffer, mark: pointer, where: var GtkTextIter) {.importc: "gtk_text_buffer_add_mark", libgtk3.}
+# proc gtk_text_buffer_get_insert*(buffer: pointer): pointer {.importc: "gtk_text_buffer_get_insert", libgtk3.}
+# proc gtk_text_buffer_get_iter_at_line*(buffer: pointer, iter: var GtkTextIter, line_number: cint) {.importc: "gtk_text_buffer_get_iter_at_line", libgtk3.}
+proc gtk_text_buffer_insert*(buffer: pointer, iter: var GtkTextIter, text: cstring, len: cint) {.importc: "gtk_text_buffer_insert", libgtk3.}
+
+proc gtk_scrolled_window_new*(hadjustment, vadjustment: pointer): pointer {.importc: "gtk_scrolled_window_new", libgtk3.}
+proc gtk_scrolled_window_set_policy*(scrolled_window: pointer, hscrollbar_policy, vscrollbar_policy: cint) {.importc: "gtk_scrolled_window_set_policy", libgtk3.}
+proc gtk_scrolled_window_get_hscrollbar*(scrolled_window: pointer): pointer {.importc: "gtk_scrolled_window_get_hscrollbar", libgtk3.}
+proc gtk_scrolled_window_get_vscrollbar*(scrolled_window: pointer): pointer {.importc: "gtk_scrolled_window_get_vscrollbar", libgtk3.}
+proc gtk_scrolled_window_get_hadjustment*(scrolled_window: pointer): pointer {.importc: "gtk_scrolled_window_get_hadjustment", libgtk3.}
+proc gtk_scrolled_window_get_vadjustment*(scrolled_window: pointer): pointer {.importc: "gtk_scrolled_window_get_vadjustment", libgtk3.}
+# proc gtk_scrolled_window_get_max_content_width*(scrolled_window: pointer): cint {.importc: "gtk_scrolled_window_get_max_content_width", libgtk3.}
+# proc gtk_scrolled_window_get_min_content_width*(scrolled_window: pointer): cint {.importc: "gtk_scrolled_window_get_min_content_width", libgtk3.}
+# proc gtk_scrolled_window_set_overlay_scrolling*(scrolled_window: pointer, overlay_scrolling: bool) {.importc: "gtk_scrolled_window_set_overlay_scrolling", libgtk3.}
+
+proc gtk_frame_new*(label: cstring): pointer {.importc: "gtk_frame_new", libgtk3.}
+proc gtk_frame_set_label*(frame: pointer, label: cstring) {.importc: "gtk_frame_set_label", libgtk3.}
+proc gtk_frame_get_label_widget*(frame: pointer): pointer {.importc: "gtk_frame_get_label_widget", libgtk3.}
+
+proc gtk_drawing_area_new*(): pointer {.importc: "gtk_drawing_area_new", libgtk3.}
+
+proc gtk_adjustment_get_value*(adjustment: pointer): cdouble {.importc: "gtk_adjustment_get_value", libgtk3.}
+proc gtk_adjustment_set_value*(adjustment: pointer, value: cdouble) {.importc: "gtk_adjustment_set_value", libgtk3.}
+proc gtk_adjustment_set_upper*(adjustment: pointer, upper: cdouble) {.importc: "gtk_adjustment_set_upper", libgtk3.}
+proc gtk_adjustment_get_upper*(adjustment: pointer): cdouble {.importc: "gtk_adjustment_get_upper", libgtk3.}
+proc gtk_adjustment_set_page_size*(adjustment: pointer, page_size: cdouble) {.importc: "gtk_adjustment_set_page_size", libgtk3.}
+proc gtk_adjustment_get_page_size*(adjustment: pointer): cdouble {.importc: "gtk_adjustment_get_page_size", libgtk3.}
+
+proc gtk_drag_dest_set*(widget: pointer, flags: cint, targets: pointer, n_targets: cint, actions: cint) {.importc: "gtk_drag_dest_set", libgtk3.}
+
+proc gdk_keyval_to_unicode*(keyval: cint): cint {.importc: "gdk_keyval_to_unicode", libgtk3.}
+
+proc gdk_screen_get_default*(): pointer {.importc: "gdk_screen_get_default", libgtk3.}
+proc gdk_screen_get_primary_monitor*(screen: pointer): cint {.importc: "gdk_screen_get_primary_monitor", libgtk3.}
+# proc gdk_screen_get_width*(screen: pointer): cint {.importc: "gdk_screen_get_width", libgtk3.}
+# proc gdk_screen_get_height*(screen: pointer): cint {.importc: "gdk_screen_get_height", libgtk3.}
+proc gdk_screen_get_monitor_workarea*(screen: pointer, monitor_num: cint, dest: var GdkRectangle) {.importc: "gdk_screen_get_monitor_workarea", libgtk3.}
+
+proc gtk_style_context_get_padding*(context: pointer, state: cint, padding: var GtkBorder) {.importc: "gtk_style_context_get_padding", libgtk3.}
+proc gtk_style_context_get_background_color*(context: pointer, state: cint, color: var GdkRGBA) {.importc: "gtk_style_context_get_background_color", libgtk3.}
+proc gtk_style_context_get_color*(context: pointer, state: cint, color: var GdkRGBA) {.importc: "gtk_style_context_get_color", libgtk3.}
+# proc gtk_style_context_get_font*(context: pointer, state: cint): pointer {.importc: "gtk_style_context_get_font", libgtk3.}
+
+proc gtk_border_new*(): pointer {.importc: "gtk_border_new", libgtk3.}
+
+# proc gdk_threads_init*() {.importc: "gdk_threads_init", libgtk3.}
+# proc gdk_threads_add_idle*(function, data: pointer): cint {.importc: "gdk_threads_add_idle", libgtk3.}
+
+proc gtk_scrollbar_new*(orientation: cint, adjustment: pointer): pointer {.importc: "gtk_scrollbar_new", libgtk3.}
+
+proc gtk_adjustment_new*(value, lower, upper, step_increment, page_increment, page_size: cdouble): pointer {.importc: "gtk_adjustment_new", libgtk3.}
+
+# proc gtk_selection_data_get_length*(selection_data: pointer): cint {.importc: "gtk_selection_data_get_length", libgtk3.}
+# proc gtk_selection_data_get_text*(selection_data: pointer): cstring {.importc: "gtk_selection_data_get_text", libgtk3.}
+
+proc gtk_selection_data_get_uris*(selection_data: pointer): ptr cstring {.importc: "gtk_selection_data_get_uris", libgtk3.}
+proc g_filename_from_uri*(uri: pointer): cstring {.importc: "g_filename_from_uri", libgtk3.}
+
+
+# ----------------------------------------------------------------------------------------
+# Drawing Related Procs
+# ----------------------------------------------------------------------------------------
+
+proc gtk_widget_create_pango_layout*(widget: pointer, text: cstring): pointer {.importc: "gtk_widget_create_pango_layout", libgtk3.}
+proc gdk_cairo_set_source_rgba*(cr: pointer, rgba: var GdkRGBA) {.importc: "gdk_cairo_set_source_rgba", libgtk3.}
+proc gdk_cairo_surface_create_from_pixbuf*(pixbuf: pointer, scale: cint, for_window: pointer): pointer {.importc: "gdk_cairo_surface_create_from_pixbuf", libgtk3.}
+proc gdk_pixbuf_new_from_file*(filename: cstring, error: pointer): pointer {.importc: "gdk_pixbuf_new_from_file", libgtk3.}
+proc gdk_pixbuf_save*(pixbuf: pointer, filename, `type`: cstring, error: pointer, param5, param6, param7: cstring): bool {.importc: "gdk_pixbuf_save", libgtk3.}
+proc gdk_pixbuf_get_from_surface*(surface: pointer, src_x, src_y, width, height: cint): pointer {.importc: "gdk_pixbuf_get_from_surface", libgtk3.}
+# proc gdk_pixmap_create_from_data*(drawable, data: pointer, width, height, depth: cint, fg, bg: var GdkRGBA): pointer {.importc: "gdk_pixmap_create_from_data", libgtk3.}
+
+proc cairo_image_surface_create*(format: cint, width, height: cint): pointer {.importc: "cairo_image_surface_create", libgtk3.}
+# proc cairo_image_surface_create_for_data*(data: pointer, format: cairo_format_t, width, height, stride: cint): pointer {.importc: "cairo_image_surface_create_for_data", libgtk3.}
+proc cairo_image_surface_get_width*(surface: pointer): cint {.importc: "cairo_image_surface_get_width", libgtk3.}
+proc cairo_image_surface_get_height*(surface: pointer): cint {.importc: "cairo_image_surface_get_height", libgtk3.}
+proc cairo_image_surface_get_stride*(surface: pointer): cint {.importc: "cairo_image_surface_get_stride", libgtk3.}
+proc cairo_image_surface_get_data*(surface: pointer): cstring {.importc: "cairo_image_surface_get_data", libgtk3.}
+proc cairo_surface_flush*(surface: pointer) {.importc: "cairo_surface_flush", libgtk3.}
+proc cairo_surface_mark_dirty*(surface: pointer) {.importc: "cairo_surface_mark_dirty", libgtk3.}
+proc cairo_surface_destroy*(surface: pointer) {.importc: "cairo_surface_destroy", libgtk3.}
+
+# proc cairo_format_stride_for_width*(format: cairo_format_t, width: cint): cint {.importc: "cairo_format_stride_for_width", libgtk3.}
+
+proc cairo_create*(target: pointer): pointer {.importc: "cairo_create", libgtk3.}
+proc cairo_get_target*(cr: pointer): pointer {.importc: "cairo_get_target", libgtk3.}
+proc cairo_set_source_rgb*(cr: pointer, red, green, blue: cdouble) {.importc: "cairo_set_source_rgb", libgtk3.}
+proc cairo_set_source_surface*(cr, surface: pointer, x, y: cdouble) {.importc: "cairo_set_source_surface", libgtk3.}
+proc cairo_fill*(cr: pointer) {.importc: "cairo_fill", libgtk3.}
+proc cairo_stroke*(cr: pointer) {.importc: "cairo_stroke", libgtk3.}
+proc cairo_rectangle*(cr: pointer, x, y, width, height: cdouble) {.importc: "cairo_rectangle", libgtk3.}
+proc cairo_line_to*(cr: pointer, x, y: cdouble) {.importc: "cairo_line_to", libgtk3.}
+proc cairo_move_to*(cr: pointer, x, y: cdouble) {.importc: "cairo_move_to", libgtk3.}
+proc cairo_set_line_width*(cr: pointer, width: cdouble) {.importc: "cairo_set_line_width", libgtk3.}
+# proc cairo_image_surface_create_from_png*(filename: cstring): pointer {.importc: "cairo_image_surface_create_from_png", libgtk3.}
+proc cairo_paint*(cr: pointer) {.importc: "cairo_paint", libgtk3.}
+proc cairo_scale*(cr: pointer, x, y: cdouble) {.importc: "cairo_scale", libgtk3.}
+proc cairo_translate*(cr: pointer, tx, ty: cdouble) {.importc: "cairo_translate", libgtk3.}
+# proc cairo_set_antialias*(cr: pointer, antialias: cint) {.importc: "cairo_set_antialias", libgtk3.}
+proc cairo_save*(cr: pointer) {.importc: "cairo_save", libgtk3.}
+proc cairo_restore*(cr: pointer) {.importc: "cairo_restore", libgtk3.}
+
+proc pango_cairo_show_layout*(cr, layout: pointer) {.importc: "pango_cairo_show_layout", libgtk3.}
+proc pango_cairo_create_layout*(cr: pointer): pointer {.importc: "pango_cairo_create_layout", libgtk3.}
+proc pango_layout_set_text*(layout: pointer, text: cstring, length: cint) {.importc: "pango_layout_set_text", libgtk3.}
+proc pango_layout_get_pixel_size*(layout: pointer, width, height: var cint) {.importc: "pango_layout_get_pixel_size", libgtk3.}
+proc pango_layout_set_font_description*(layout, desc: pointer) {.importc: "pango_layout_set_font_description", libgtk3.}
+proc pango_font_description_new*(): pointer {.importc: "pango_font_description_new", libgtk3.}
+proc pango_font_description_set_family*(desc: pointer, family: cstring) {.importc: "pango_font_description_set_family", libgtk3.}
+proc pango_font_description_set_size*(desc: pointer, size: cint) {.importc: "pango_font_description_set_size", libgtk3.}
+# proc pango_font_description_get_size*(desc: pointer): cint {.importc: "pango_font_description_get_size", libgtk3.}
+# proc pango_layout_set_markup*(layout: pointer, markup: cstring, length: cint) {.importc: "pango_layout_set_markup", libgtk3.}
+# proc pango_layout_new*(context: pointer): pointer {.importc: "pango_layout_new", libgtk3.}
+
+
diff --git a/src/gtk3/nigui_platform_impl.nim b/src/gtk3/nigui_platform_impl.nim
new file mode 100755
index 0000000..fbb7b45
--- /dev/null
+++ b/src/gtk3/nigui_platform_impl.nim
@@ -0,0 +1,1176 @@
+# NiGui - GTK+ 3 platform-specific code - part 3
+
+# This file will be included in "nigui.nim".
+
+# Imports:
+# math, os, strutils, times are imported by nigui.nim
+import gtk3
+import tables
+import hashes
+
+
+# ----------------------------------------------------------------------------------------
+# Internal Things
+# ----------------------------------------------------------------------------------------
+
+const pFontSizeFactor = 700
+
+# needed to calculate clicks:
+var pLastMouseButtonDownControl: ControlImpl
+var pLastMouseButtonDownControlX: int
+var pLastMouseButtonDownControlY: int
+
+proc pRaiseGError(error: ptr GError) =
+ if error == nil:
+ raiseError("Unkown error")
+ raiseError($error.message, false)
+
+proc pColorToGdkRGBA(color: Color, rgba: var GdkRGBA) =
+ rgba.red = color.red.float / 255
+ rgba.green = color.green.float / 255
+ rgba.blue = color.blue.float / 255
+ rgba.alpha = color.alpha.float / 255
+
+proc pGdkRGBAToColor(rgba: var GdkRGBA): Color =
+ result.red = uint8(rgba.red.float * 255)
+ result.green = uint8(rgba.green.float * 255)
+ result.blue = uint8(rgba.blue.float * 255)
+ result.alpha = uint8(rgba.alpha.float * 255)
+
+proc pWindowDeleteSignal(widgetHandle, event, data: pointer): bool {.cdecl.} =
+ let window = cast[WindowImpl](data)
+ window.dispose()
+ return true
+
+proc pWindowConfigureSignal(windowHandle, event, data: pointer): bool {.cdecl.} =
+ # called on resize and move
+ let window = cast[WindowImpl](data)
+ var x, y: cint
+ gtk_window_get_position(window.fHandle, x, y)
+ window.fX = x
+ window.fY = y
+ var width, height: cint
+ gtk_window_get_size(window.fHandle, width, height)
+ window.fWidth = width
+ window.fHeight = height
+ window.fClientWidth = width
+ window.fClientHeight = height
+ window.triggerRelayout()
+
+proc pKeyvalToKey(keyval: cint): Key =
+ case keyval
+ of 48: Key_Number0
+ of 49: Key_Number1
+ of 50: Key_Number2
+ of 51: Key_Number3
+ of 52: Key_Number4
+ of 53: Key_Number5
+ of 54: Key_Number6
+ of 55: Key_Number7
+ of 56: Key_Number8
+ of 57: Key_Number9
+ of 65: Key_A
+ of 66: Key_B
+ of 67: Key_C
+ of 68: Key_D
+ of 69: Key_E
+ of 70: Key_F
+ of 71: Key_G
+ of 72: Key_H
+ of 73: Key_I
+ of 74: Key_J
+ of 75: Key_K
+ of 76: Key_L
+ of 77: Key_M
+ of 78: Key_N
+ of 79: Key_O
+ of 80: Key_P
+ of 81: Key_Q
+ of 82: Key_R
+ of 83: Key_S
+ of 84: Key_T
+ of 85: Key_U
+ of 86: Key_V
+ of 87: Key_W
+ of 88: Key_X
+ of 89: Key_Y
+ of 90: Key_Z
+ of 97: Key_A
+ of 98: Key_B
+ of 99: Key_C
+ of 100: Key_D
+ of 101: Key_E
+ of 102: Key_F
+ of 103: Key_G
+ of 104: Key_H
+ of 105: Key_I
+ of 106: Key_J
+ of 107: Key_K
+ of 108: Key_L
+ of 109: Key_M
+ of 110: Key_N
+ of 111: Key_O
+ of 112: Key_P
+ of 113: Key_Q
+ of 114: Key_R
+ of 115: Key_S
+ of 116: Key_T
+ of 117: Key_U
+ of 118: Key_V
+ of 119: Key_W
+ of 120: Key_X
+ of 121: Key_Y
+ of 122: Key_Z
+ of 32: Key_Space
+ of 65289: Key_Tab
+ of 65293: Key_Return
+ of 65307: Key_Escape
+ of 65379: Key_Insert
+ of 65535: Key_Delete
+ of 65288: Key_Backspace
+ of 65361: Key_Left
+ of 65362: Key_Up
+ of 65363: Key_Right
+ of 65364: Key_Down
+ of 65360: Key_Home
+ of 65367: Key_End
+ of 65365: Key_PageUp
+ of 65366: Key_PageDown
+ else: Key_None
+
+proc pWindowKeyPressSignal(widget: pointer, event: var GdkEventKey, data: pointer): bool {.cdecl.} =
+ # echo "window keyPressCallback"
+
+ # echo event.keyval
+ # echo event.hardware_keycode
+
+ # echo $gdk_keyval_to_unicode(event.keyval)
+ var unicode = gdk_keyval_to_unicode(event.keyval)
+ # if unicode == 0:
+ # unicode = event.keyval
+
+ let window = cast[WindowImpl](data)
+ var evt = new WindowKeyEvent
+ evt.window = window
+ evt.key = pKeyvalToKey(event.keyval)
+ if evt.key == Key_None:
+ echo "Unkown key value: ", event.keyval
+ return
+
+ evt.character = $event.`string`
+ evt.unicode = unicode
+
+ try:
+ window.handleKeyDownEvent(evt)
+ except:
+ handleException()
+
+proc pControlKeyPressSignal(widget: pointer, event: var GdkEventKey, data: pointer): bool {.cdecl.} =
+
+ # echo "control keyPressCallback"
+
+ # echo $gdk_keyval_to_unicode(event.keyval)
+ var unicode = gdk_keyval_to_unicode(event.keyval)
+ # if unicode == 0:
+ # unicode = event.keyval
+
+ let control = cast[ControlImpl](data)
+ var evt = new ControlKeyEvent
+ evt.control = control
+ evt.key = pKeyvalToKey(event.keyval)
+ if evt.key == Key_None:
+ echo "Unkown key value: ", event.keyval
+ return
+ evt.character = $event.`string`
+ evt.unicode = unicode
+
+ try:
+ control.handleKeyDownEvent(evt)
+ except:
+ handleException()
+
+ return evt.cancel
+
+method focus(control: ControlImpl) =
+ gtk_widget_grab_focus(control.fHandle)
+
+method focus(control: NativeTextArea) =
+ gtk_widget_grab_focus(control.fTextViewHandle)
+
+proc pDefaultControlButtonPressSignal(widget: pointer, event: var GdkEventButton, data: pointer): bool {.cdecl.} =
+ let control = cast[ControlImpl](data)
+ let x = event.x.int
+ let y = event.y.int
+ var evt = new MouseButtonEvent
+ evt.control = control
+ case event.button
+ of 1: evt.button = MouseButton_Left
+ of 2: evt.button = MouseButton_Middle
+ of 3: evt.button = MouseButton_Right
+ else: return # TODO
+ evt.x = x
+ evt.y = y
+
+ try:
+ control.handleMouseButtonDownEvent(evt)
+ except:
+ handleException()
+
+ pLastMouseButtonDownControl = control
+ pLastMouseButtonDownControlX = x
+ pLastMouseButtonDownControlY = y
+
+proc pCustomControlButtonPressSignal(widget: pointer, event: var GdkEventButton, data: pointer): bool {.cdecl.} =
+ discard pDefaultControlButtonPressSignal(widget, event, data)
+ let control = cast[ControlImpl](data)
+ control.focus()
+ result = true # Stop propagation, required to detect clicks
+
+proc pControlButtonReleaseSignal(widget: pointer, event: var GdkEventButton, data: pointer): bool {.cdecl.} =
+ let control = cast[ControlImpl](data)
+ let x = event.x.int
+ let y = event.y.int
+ if not (x >= 0 and x < control.width and y >= 0 and y < control.height):
+ return
+ var evt = new MouseButtonEvent
+ evt.control = control
+ case event.button
+ of 1: evt.button = MouseButton_Left
+ of 2: evt.button = MouseButton_Middle
+ of 3: evt.button = MouseButton_Right
+ else: return # TODO
+ evt.x = x
+ evt.y = y
+ control.handleMouseButtonUpEvent(evt)
+ if event.button == 1 and control == pLastMouseButtonDownControl and abs(x - pLastMouseButtonDownControlX) <= clickMaxXYMove and abs(y - pLastMouseButtonDownControlY) <= clickMaxXYMove:
+ var clickEvent = new ClickEvent
+ clickEvent.control = control
+ try:
+ control.handleClickEvent(clickEvent)
+ except:
+ handleException()
+ # result = true # stop propagation
+
+proc pControlChangedSignal(widget: pointer, data: pointer): bool {.cdecl.} =
+ let control = cast[ControlImpl](data)
+ var evt = new TextChangeEvent
+ try:
+ control.handleTextChangeEvent(evt)
+ except:
+ handleException()
+
+# proc pTextAreaEndUserActionSignal(widget: pointer, data: pointer): bool {.cdecl.} =
+ # let control = cast[ControlImpl](data)
+ # discard
+
+proc pSetDragDest(widget: pointer) =
+ var target: GtkTargetEntry
+ target.target = "text/uri-list"
+ target.flags = 0
+ target.info = 0
+ gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, target.addr, 1, GDK_ACTION_COPY)
+
+
+# ----------------------------------------------------------------------------------------
+# App Procedures
+# ----------------------------------------------------------------------------------------
+
+proc init(app: App) =
+ gtk_init(nil, nil)
+
+ # Determine default styles:
+ var window = gtk_window_new(GTK_WINDOW_TOPLEVEL)
+ var context = gtk_widget_get_style_context(window)
+ var rgba: GdkRGBA
+ gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, rgba)
+ app.defaultBackgroundColor = rgba.pGdkRGBAToColor()
+ gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, rgba)
+ app.defaultTextColor = rgba.pGdkRGBAToColor()
+ gtk_widget_destroy(window)
+
+proc runMainLoop() = gtk_main()
+
+proc processEvents(app: App) =
+ while gtk_events_pending() == 1:
+ discard gtk_main_iteration()
+
+
+# ----------------------------------------------------------------------------------------
+# Dialogs
+# ----------------------------------------------------------------------------------------
+
+proc alert(window: Window, message: string, title = "Message") =
+ var dialog = gtk_dialog_new()
+ gtk_window_set_title(dialog, title)
+ gtk_window_resize(dialog, 200, 70)
+ let contentArea = gtk_dialog_get_content_area(dialog)
+ gtk_container_set_border_width(contentArea, 15)
+ var label = gtk_label_new(message)
+ gtk_widget_show(label)
+ gtk_box_pack_start(contentArea, label, 0, 0, 0)
+ let actionArea = gtk_dialog_get_action_area(dialog)
+ gtk_button_box_set_layout(actionArea, GTK_BUTTONBOX_EXPAND)
+ gtk_widget_set_margin_top(actionArea, 15)
+ discard gtk_dialog_add_button(dialog, "OK", 1)
+ if window != nil:
+ gtk_window_set_transient_for(dialog, cast[WindowImpl](window).fHandle)
+ discard gtk_dialog_run(dialog)
+ gtk_widget_hide(dialog)
+
+method run*(dialog: OpenFileDialog) =
+ dialog.files = @[]
+ var chooser = gtk_file_chooser_dialog_new(nil, nil, GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, nil)
+ # Issue: When a title is passed, the dialog is shown without a title
+ discard gtk_file_chooser_set_current_folder(chooser, dialog.directory)
+ gtk_file_chooser_set_select_multiple(chooser, dialog.multiple)
+ let res = gtk_dialog_run(chooser)
+ if res == GTK_RESPONSE_ACCEPT:
+ let list = gtk_file_chooser_get_filenames(chooser)
+ let count = g_slist_length(list)
+ for i in 0..count - 1:
+ dialog.files.add($g_slist_nth_data_string(list, i))
+ gtk_widget_destroy(chooser)
+
+method run(dialog: SaveFileDialog) =
+ var chooser = gtk_file_chooser_dialog_new(nil, nil, GTK_FILE_CHOOSER_ACTION_SAVE, "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, nil)
+ # Issue: When a title is passed, the dialog is shown without a title
+ let res = gtk_dialog_run(chooser)
+ discard gtk_file_chooser_set_current_folder(chooser, dialog.directory)
+ if dialog.defaultName.len > 0:
+ discard gtk_file_chooser_set_current_name(chooser, dialog.defaultName) # Issue: does not work
+ if res == GTK_RESPONSE_ACCEPT:
+ dialog.file = $gtk_file_chooser_get_filename(chooser)
+ else:
+ dialog.file = ""
+ gtk_widget_destroy(chooser)
+
+
+# ----------------------------------------------------------------------------------------
+# Timers
+# ----------------------------------------------------------------------------------------
+
+type TimerEntry = object
+ timerInternalId: cint
+ timerProc: TimerProc
+ data: pointer
+
+var
+ pTimers = initTable[int64, TimerEntry]()
+ pNextTimerId: int = 1
+
+proc pTimerFunction(timer: Timer): bool {.cdecl.} =
+ let timerEntry = pTimers.getOrDefault(cast[int](timer))
+ var event = new TimerEvent
+ event.timer = timer
+ event.data = timerEntry.data
+ timerEntry.timerProc(event)
+ pTimers.del(cast[int](timer))
+ # result is false to stop timer
+
+proc pRepeatingTimerFunction(timer: Timer): bool {.cdecl.} =
+ let timerEntry = pTimers.getOrDefault(cast[int](timer))
+ var event = new TimerEvent
+ event.timer = timer
+ event.data = timerEntry.data
+ timerEntry.timerProc(event)
+ result = true # repeat timer
+
+proc startTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer =
+ var timerEntry: TimerEntry
+ timerEntry.timerInternalId = g_timeout_add(milliSeconds.cint, pTimerFunction, cast[pointer](pNextTimerId))
+ timerEntry.timerProc = timerProc
+ timerEntry.data = data
+ pTimers[pNextTimerId] = timerEntry
+ result = cast[Timer](pNextTimerId)
+ pNextTimerId.inc()
+ if pNextTimerId == inactiveTimer:
+ pNextTimerId.inc()
+
+proc startRepeatingTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer =
+ var timerEntry: TimerEntry
+ timerEntry.timerInternalId = g_timeout_add(milliSeconds.cint, pRepeatingTimerFunction, cast[pointer](pNextTimerId))
+ timerEntry.timerProc = timerProc
+ timerEntry.data = data
+ pTimers[pNextTimerId] = timerEntry
+ result = cast[Timer](pNextTimerId)
+ pNextTimerId.inc()
+ if pNextTimerId == inactiveTimer:
+ pNextTimerId.inc()
+
+proc stop(timer: var Timer) =
+ if cast[int](timer) != inactiveTimer:
+ let timerEntry = pTimers.getOrDefault(cast[int](timer))
+ pTimers.del(cast[int](timer))
+ discard g_source_remove(timerEntry.timerInternalId)
+ timer = cast[Timer](inactiveTimer)
+
+# ----------------------------------------------------------------------------------------
+# Canvas
+# ----------------------------------------------------------------------------------------
+
+proc pUpdateFont(canvas: Canvas) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ canvasImpl.fFont = pango_font_description_new()
+ pango_font_description_set_family(canvasImpl.fFont, canvas.fontFamily)
+ pango_font_description_set_size(canvasImpl.fFont, cint(canvas.fontSize * pFontSizeFactor))
+
+method drawText(canvas: Canvas, text: string, x, y = 0) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ let cr = canvasImpl.fCairoContext
+ if cr == nil:
+ raiseError("Canvas is not in drawing state.")
+ var rgba: GdkRGBA
+ canvas.textColor.pColorToGdkRGBA(rgba)
+ gdk_cairo_set_source_rgba(cr, rgba)
+
+ var layout = pango_cairo_create_layout(cr)
+ pango_layout_set_text(layout, text, text.len.cint)
+
+ if canvasImpl.fFont == nil:
+ canvas.pUpdateFont()
+ pango_layout_set_font_description(layout, canvasImpl.fFont)
+
+ cairo_move_to(cr, x.float, y.float)
+ pango_cairo_show_layout(cr, layout)
+
+method drawLine(canvas: Canvas, x1, y1, x2, y2: int) =
+ let cr = cast[CanvasImpl](canvas).fCairoContext
+ if cr == nil:
+ raiseError("Canvas is not in drawing state.")
+ var rgba: GdkRGBA
+ canvas.lineColor.pColorToGdkRGBA(rgba)
+ gdk_cairo_set_source_rgba(cr, rgba)
+ cairo_move_to(cr, x1.float, y1.float)
+ cairo_line_to(cr, x2.float, y2.float)
+ cairo_stroke(cr)
+
+method drawRectArea(canvas: Canvas, x, y, width, height: int) =
+ let cr = cast[CanvasImpl](canvas).fCairoContext
+ if cr == nil:
+ raiseError("Canvas is not in drawing state.")
+ var rgba: GdkRGBA
+ canvas.areaColor.pColorToGdkRGBA(rgba)
+ gdk_cairo_set_source_rgba(cr, rgba)
+ cairo_rectangle(cr, x.float, y.float, width.float, height.float)
+ cairo_fill(cr)
+
+method drawRectOutline(canvas: Canvas, x, y, width, height: int) =
+ let cr = cast[CanvasImpl](canvas).fCairoContext
+ if cr == nil:
+ raiseError("Canvas is not in drawing state.")
+ var rgba: GdkRGBA
+ canvas.lineColor.pColorToGdkRGBA(rgba)
+ gdk_cairo_set_source_rgba(cr, rgba)
+ cairo_rectangle(cr, x.float, y.float, width.float, height.float)
+ cairo_set_line_width(cr, 1)
+ cairo_stroke(cr)
+
+method drawImage(canvas: Canvas, image: Image, x, y = 0, width, height = -1) =
+ let cr = cast[CanvasImpl](canvas).fCairoContext
+ if cr == nil:
+ raiseError("Canvas is not in drawing state.")
+ let imageCanvas = cast[CanvasImpl](image.canvas)
+ if imageCanvas.fSurface == nil:
+ raiseError("Image is not initialized.")
+ var drawWith = image.width
+ var drawHeight = image.height
+ if width != -1:
+ drawWith = width
+ drawHeight = int(drawHeight * drawWith / image.width)
+ if height != -1:
+ drawHeight = height
+ if drawWith == image.width and drawHeight == image.height:
+ cairo_set_source_surface(cr, imageCanvas.fSurface, x.cdouble, y.cdouble)
+ cairo_paint(cr)
+ else:
+ cairo_save(cr)
+ cairo_translate(cr, x.cdouble, y.cdouble)
+ cairo_scale(cr, drawWith / image.width, drawHeight / image.height)
+ cairo_set_source_surface(cr, imageCanvas.fSurface, 0, 0)
+ cairo_paint(cr)
+ cairo_restore(cr)
+
+method setPixel(canvas: Canvas, x, y: int, color: Color) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ let cr = canvasImpl.fCairoContext
+ if cr == nil:
+ raiseError("Canvas is not in drawing state.")
+ if canvasImpl.fData == nil:
+ # For a Canvas of a Control we can't access the memory directly, so draw a rectangle (slower)
+ var rgba: GdkRGBA
+ color.pColorToGdkRGBA(rgba)
+ gdk_cairo_set_source_rgba(cr, rgba)
+ cairo_rectangle(cr, x.float, y.float, 1, 1)
+ cairo_fill(cr)
+ else:
+ # For a Canvas of an Image we can write in the memory directly (faster)
+ if x < 0 or y < 0 or x >= canvas.width or y >= canvas.height:
+ raiseError("Pixel is out of range.")
+ cairo_surface_flush(canvasImpl.fSurface)
+ let i = y * canvasImpl.fStride + x * 4
+ canvasImpl.fData[i + 0] = color.blue.chr
+ canvasImpl.fData[i + 1] = color.green.chr
+ canvasImpl.fData[i + 2] = color.red.chr
+ canvasImpl.fData[i + 3] = 255.chr
+ cairo_surface_mark_dirty(canvasImpl.fSurface)
+
+method `fontFamily=`(canvas: CanvasImpl, fontFamily: string) =
+ procCall canvas.Canvas.`fontFamily=`(fontFamily)
+ canvas.fFont = nil
+
+method `fontSize=`(canvas: CanvasImpl, fontSize: int) =
+ procCall canvas.Canvas.`fontSize=`(fontSize)
+ canvas.fFont = nil
+
+method getTextLineWidth(canvas: CanvasImpl, text: string): int =
+ if canvas.fCairoContext == nil:
+ raiseError("Canvas is not in drawing state.")
+ var layout = pango_cairo_create_layout(canvas.fCairoContext)
+ pango_layout_set_text(layout, text, text.len.cint)
+ if canvas.fFont == nil:
+ canvas.pUpdateFont()
+ pango_layout_set_font_description(layout, canvas.fFont)
+ var width: cint = 0
+ var height: cint = 0
+ pango_layout_get_pixel_size(layout, width, height)
+ result = width
+
+method getTextLineHeight(canvas: CanvasImpl): int =
+ if canvas.fCairoContext == nil:
+ raiseError("Canvas is not in drawing state.")
+ var layout = pango_cairo_create_layout(canvas.fCairoContext)
+ pango_layout_set_text(layout, "a", 1)
+ if canvas.fFont == nil:
+ canvas.pUpdateFont()
+ pango_layout_set_font_description(layout, canvas.fFont)
+ var width: cint = 0
+ var height: cint = 0
+ pango_layout_get_pixel_size(layout, width, height)
+ result = height
+
+
+# ----------------------------------------------------------------------------------------
+# Image
+# ----------------------------------------------------------------------------------------
+
+method resize(image: Image, width, height: int) =
+ let canvas = cast[CanvasImpl](image.fCanvas)
+ if canvas.fSurface != nil:
+ cairo_surface_destroy(canvas.fSurface)
+ canvas.fSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width.cint, height.cint)
+ canvas.fCairoContext = cairo_create(canvas.fSurface)
+ canvas.fData = cairo_image_surface_get_data(canvas.fSurface)
+ canvas.fStride = cairo_image_surface_get_stride(canvas.fSurface)
+ image.canvas.fWidth = width
+ image.canvas.fHeight = height
+
+method loadFromFile(image: Image, filePath: string) =
+ let canvas = cast[CanvasImpl](image.fCanvas)
+ if canvas.fSurface != nil:
+ cairo_surface_destroy(canvas.fSurface)
+ var error: ptr GError
+ var pixbuf = gdk_pixbuf_new_from_file(filePath, error.addr)
+ if pixbuf == nil:
+ pRaiseGError(error)
+ canvas.fSurface = gdk_cairo_surface_create_from_pixbuf(pixbuf, 1, nil)
+ canvas.fCairoContext = cairo_create(canvas.fSurface)
+ canvas.fData = cairo_image_surface_get_data(canvas.fSurface)
+ canvas.fStride = cairo_image_surface_get_stride(canvas.fSurface)
+ image.canvas.fWidth = cairo_image_surface_get_width(canvas.fSurface)
+ image.canvas.fHeight = cairo_image_surface_get_height(canvas.fSurface)
+
+method saveToPngFile(image: Image, filePath: string) =
+ let canvas = cast[CanvasImpl](image.fCanvas)
+ var pixbuf = gdk_pixbuf_get_from_surface(canvas.fSurface, 0, 0, image.width.cint, image.height.cint)
+ var error: ptr GError
+ if not gdk_pixbuf_save(pixbuf, filePath, "png", error.addr, nil, nil, nil):
+ pRaiseGError(error)
+
+method saveToJpegFile(image: Image, filePath: string, quality = 80) =
+ let canvas = cast[CanvasImpl](image.fCanvas)
+ var pixbuf = gdk_pixbuf_get_from_surface(canvas.fSurface, 0, 0, image.width.cint, image.height.cint)
+ var error: ptr GError
+ if not gdk_pixbuf_save(pixbuf, filePath, "jpeg", error.addr, "quality", $quality, nil):
+ pRaiseGError(error)
+
+
+# ----------------------------------------------------------------------------------------
+# Window
+# ----------------------------------------------------------------------------------------
+
+proc pWindowDragDataReceivedSignal(widget, context: pointer, x, y: cint, data: pointer, info, time: cint, user_data: pointer) {.cdecl.} =
+ let window = cast[WindowImpl](user_data)
+ var files: seq[string] = @[]
+ var p = gtk_selection_data_get_uris(data)
+ while p[] != nil:
+ files.add($g_filename_from_uri(p[]))
+ p = cast[ptr cstring](cast[int](p) + 8)
+ var event = new DropFilesEvent
+ event.window = window
+ event.files = files
+ window.handleDropFilesEvent(event)
+
+proc pMainScrollbarDraw(widget: pointer, cr: pointer, data: pointer): bool {.cdecl.} =
+ # This proc is there to get the scrollbar size
+ if fScrollbarSize == -1:
+ var scrollbar = gtk_scrolled_window_get_hscrollbar(widget)
+ var allocation: GdkRectangle
+ gtk_widget_get_allocation(scrollbar, allocation)
+ gtk_scrolled_window_set_policy(widget, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC)
+ fScrollbarSize = allocation.height
+
+proc init(window: WindowImpl) =
+ window.fHandle = gtk_window_new(GTK_WINDOW_TOPLEVEL)
+ window.fInnerHandle = gtk_scrolled_window_new(nil, nil)
+ gtk_widget_show(window.fInnerHandle)
+ gtk_container_add(window.fHandle, window.fInnerHandle)
+ window.Window.init()
+ discard g_signal_connect_data(window.fHandle, "delete-event", pWindowDeleteSignal, cast[pointer](window))
+ discard g_signal_connect_data(window.fHandle, "configure-event", pWindowConfigureSignal, cast[pointer](window))
+ discard g_signal_connect_data(window.fHandle, "key-press-event", pWindowKeyPressSignal, cast[pointer](window))
+
+ # Enable drag and drop of files:
+ pSetDragDest(window.fHandle)
+ discard g_signal_connect_data(window.fHandle, "drag-data-received", pWindowDragDataReceivedSignal, cast[pointer](window))
+
+ if fScrollbarSize == -1:
+ gtk_scrolled_window_set_policy(window.fInnerHandle, GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS)
+ discard g_signal_connect_data(window.fInnerHandle, "draw", pMainScrollbarDraw, nil)
+
+method destroy(window: WindowImpl) =
+ procCall window.Window.destroy()
+ gtk_widget_destroy(window.fHandle)
+ # this destroys also child widgets
+ window.fHandle = nil
+
+method `visible=`(window: WindowImpl, visible: bool) =
+ procCall window.Window.`visible=`(visible)
+ if visible:
+ gtk_widget_show(window.fHandle)
+ while fScrollbarSize == -1:
+ discard gtk_main_iteration()
+ else:
+ gtk_widget_hide(window.fHandle)
+ app.processEvents()
+
+method showModal(window, parent: WindowImpl) =
+ gtk_window_set_modal(window.fHandle, 1)
+ gtk_window_set_transient_for(window.fHandle, parent.fHandle)
+ window.visible = true
+
+method `width=`*(window: WindowImpl, width: int) =
+ procCall window.Window.`width=`(width)
+ gtk_window_resize(window.fHandle, window.width.cint, window.height.cint)
+ window.fClientWidth = window.width
+
+method `height=`*(window: WindowImpl, height: int) =
+ procCall window.Window.`height=`(height)
+ gtk_window_resize(window.fHandle, window.width.cint, window.height.cint)
+ window.fClientHeight = window.height
+
+proc pUpdatePosition(window: WindowImpl) = gtk_window_move(window.fHandle, window.x.cint, window.y.cint)
+
+method `x=`(window: WindowImpl, x: int) =
+ procCall window.Window.`x=`(x)
+ window.pUpdatePosition()
+
+method `y=`(window: WindowImpl, y: int) =
+ procCall window.Window.`y=`(y)
+ window.pUpdatePosition()
+
+method centerOnScreen(window: WindowImpl) =
+ let screen = gdk_screen_get_default()
+ let monitor = gdk_screen_get_primary_monitor(screen)
+ var rect: GdkRectangle
+ gdk_screen_get_monitor_workarea(screen, monitor, rect)
+ window.fX = rect.x + (rect.width - window.width) div 2
+ window.fY = rect.y + (rect.height - window.height) div 2
+ window.pUpdatePosition()
+
+method `title=`(window: WindowImpl, title: string) =
+ procCall window.Window.`title=`(title)
+ gtk_window_set_title(window.fHandle, window.title.cstring)
+
+method `control=`(window: WindowImpl, control: ControlImpl) =
+ procCall window.Window.`control=`(control)
+ gtk_container_add(window.fInnerHandle, control.fHandle)
+
+method `iconPath=`(window: WindowImpl, iconPath: string) =
+ procCall window.Window.`iconPath=`(iconPath)
+ gtk_window_set_icon_from_file(window.fHandle, iconPath, nil)
+
+
+# ----------------------------------------------------------------------------------------
+# Control
+# ----------------------------------------------------------------------------------------
+
+method pUpdateScrollBar(control: ControlImpl)
+
+proc pControlDrawSignal(widget: pointer, cr: pointer, data: pointer): bool {.cdecl.} =
+ let control = cast[ControlImpl](data)
+
+ # Trigger pUpdateScrollBar() in case it's not initialized, which could be because of missing fScrollbarSize
+ if control.fHScrollbar == nil:
+ control.pUpdateScrollBar()
+
+ var event = new DrawEvent
+ event.control = control
+ var canvas = cast[CanvasImpl](control.canvas)
+ if canvas == nil:
+ canvas = newCanvas(control)
+ canvas.fCairoContext = cr
+ # canvas.fSurface = cairo_get_target(cr) # no need to set this
+ # canvas.fData = cairo_image_surface_get_data(canvas.fSurface) # does not work
+ try:
+ # var region = gdk_window_get_clip_region(control.fHandle)
+ # gdk_window_begin_paint_region(control.fHandle, region)
+ # no effect
+
+ control.handleDrawEvent(event)
+
+ # gdk_window_end_paint(control.fHandle)
+ except:
+ handleException()
+
+ canvas.fCairoContext = nil
+
+proc pControlScollXSignal(adjustment: pointer, data: pointer) {.cdecl.} =
+ let control = cast[ControlImpl](data)
+ control.fXScrollPos = gtk_adjustment_get_value(adjustment).int
+ control.forceRedraw()
+
+proc pControlScollYSignal(adjustment: pointer, data: pointer) {.cdecl.} =
+ let control = cast[ControlImpl](data)
+ control.fYScrollPos = gtk_adjustment_get_value(adjustment).int
+ control.forceRedraw()
+
+proc pUpdateFont(control: ControlImpl) =
+ var font = pango_font_description_new()
+ pango_font_description_set_family(font, control.fontFamily)
+ pango_font_description_set_size(font, cint(control.fontSize * pFontSizeFactor))
+ gtk_widget_modify_font(control.fHandle, font)
+ var rgba: GdkRGBA
+ control.textColor.pColorToGdkRGBA(rgba)
+ gtk_widget_override_color(control.fHandle, GTK_STATE_FLAG_NORMAL, rgba)
+
+method pAddButtonPressEvent(control: ControlImpl) =
+ gtk_widget_add_events(control.fHandle, GDK_BUTTON_PRESS_MASK)
+ discard g_signal_connect_data(control.fHandle, "button-press-event", pCustomControlButtonPressSignal, cast[pointer](control))
+
+proc init(control: ControlImpl) =
+
+ if control.fHandle == nil:
+ # Direct instance of ControlImpl:
+ control.fHandle = gtk_layout_new(nil, nil)
+ discard g_signal_connect_data(control.fHandle, "draw", pControlDrawSignal, cast[pointer](control))
+
+ gtk_widget_add_events(control.fHandle, GDK_KEY_PRESS_MASK)
+ discard g_signal_connect_data(control.fHandle, "key-press-event", pControlKeyPressSignal, cast[pointer](control))
+
+ control.pAddButtonPressEvent()
+
+ gtk_widget_add_events(control.fHandle, GDK_BUTTON_RELEASE_MASK)
+ discard g_signal_connect_data(control.fHandle, "button-release-event", pControlButtonReleaseSignal, cast[pointer](control))
+
+ procCall control.Control.init()
+
+method destroy(control: ControlImpl) =
+ procCall control.Control.destroy()
+ gtk_widget_destroy(control.fHandle)
+ # this destroys also child widgets
+
+method `visible=`(control: ControlImpl, visible: bool) =
+ procCall control.Control.`visible=`(visible)
+ if visible:
+ gtk_widget_show(control.fHandle)
+ else:
+ gtk_widget_hide(control.fHandle)
+
+proc dummy(widget: pointer, event: var GdkEventButton, data: pointer): bool {.cdecl.} =
+ echo "dummy"
+ result = true # Stop propagation
+
+method pUpdateScrollBar(control: ControlImpl) =
+ if control.fScrollableWidth == -1 and control.fScrollableHeight == -1:
+ return
+ echo "control.pUpdateScrollBar"
+ if control.fHScrollbar == nil:
+ if fScrollbarSize == -1:
+ return
+ # Init scrolling:
+ # echo "fScrollbarSize ", fScrollbarSize
+ control.fHAdjust = gtk_adjustment_new(0, 0, 0, 10, 10, 0)
+ control.fVAdjust = gtk_adjustment_new(0, 0, 0, 10, 10, 0)
+ control.fHScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, control.fHAdjust)
+ control.fVScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, control.fVAdjust)
+ gtk_container_add(control.fHandle, control.fHScrollbar)
+ gtk_container_add(control.fHandle, control.fVScrollbar)
+ discard g_signal_connect_data(control.fHAdjust, "value-changed", pControlScollXSignal, cast[pointer](control))
+ discard g_signal_connect_data(control.fVAdjust, "value-changed", pControlScollYSignal, cast[pointer](control))
+
+ # The dead corner is an area which just needs to be covered with a control without function and the default background color
+ control.fDeadCornerHandle = gtk_label_new("")
+ # control.fDeadCornerHandle = gtk_fixed_new()
+ gtk_container_add(control.fHandle, control.fDeadCornerHandle)
+ var rgba: GdkRGBA
+ pColorToGdkRGBA(app.defaultBackgroundColor, rgba)
+ gtk_widget_override_background_color(control.fDeadCornerHandle, GTK_STATE_FLAG_NORMAL, rgba)
+ gtk_widget_set_size_request(control.fDeadCornerHandle, fScrollbarSize.cint, fScrollbarSize.cint)
+
+ # Prevent that a click on the dead corner triggers the onClick event of the Control:
+ # gtk_widget_add_events(control.fDeadCornerHandle, GDK_BUTTON_PRESS_MASK)
+ # discard g_signal_connect_data(control.fDeadCornerHandle, "button-press-event", dummy, nil)
+ # gtk_widget_add_events(control.fDeadCornerHandle, GDK_BUTTON_RELEASE_MASK)
+ # discard g_signal_connect_data(control.fDeadCornerHandle, "button-release-event", dummy, nil)
+ # TODO: does not work. try EventBox
+
+ # Calculation of scrollbar settings:
+
+ control.fXScrollEnabled = false
+ control.fYScrollEnabled = false
+
+ if control.scrollableWidth > control.width:
+ control.fXScrollEnabled = true
+ if control.scrollableHeight > control.height:
+ control.fYScrollEnabled = true
+
+ if control.fXScrollEnabled and not control.fYScrollEnabled and control.scrollableHeight > control.height - fScrollbarSize:
+ control.fYScrollEnabled = true
+ if control.fYScrollEnabled and not control.fXScrollEnabled and control.scrollableWidth > control.width - fScrollbarSize:
+ control.fXScrollEnabled = true
+
+ # Update horizontal scrollbar:
+ if control.fXScrollEnabled:
+ var upper = control.scrollableWidth
+ var size = control.width
+ if control.fYScrollEnabled:
+ upper.inc(fScrollbarSize)
+ size.dec(fScrollbarSize)
+ gtk_adjustment_set_upper(control.fHAdjust, upper.cdouble)
+ let value = gtk_adjustment_get_value(control.fHAdjust).int
+ let maxValue = upper - control.width
+ if value > maxValue:
+ gtk_adjustment_set_value(control.fHAdjust, maxValue.float)
+ gtk_adjustment_set_page_size(control.fHAdjust, control.width.float)
+ gtk_widget_set_size_request(control.fHScrollbar, size.cint, 0)
+ gtk_layout_move(control.fHandle, control.fHScrollbar, 0, (control.height - fScrollbarSize).cint)
+ gtk_widget_show(control.fHScrollbar)
+ # Ensure that scroll pos is within range:
+ control.fXScrollPos = max(min(control.fXScrollPos, maxValue), 0)
+ else:
+ gtk_widget_hide(control.fHScrollbar)
+ control.fXScrollPos = 0
+
+ # Update vertical scrollbar:
+ if control.fYScrollEnabled:
+ var upper = control.scrollableHeight
+ var size = control.height
+ if control.fXScrollEnabled:
+ upper.inc(fScrollbarSize)
+ size.dec(fScrollbarSize)
+ gtk_adjustment_set_upper(control.fVAdjust, upper.cdouble)
+ let value = gtk_adjustment_get_value(control.fVAdjust).int
+ let maxValue = upper - control.height
+ if value > maxValue:
+ gtk_adjustment_set_value(control.fVAdjust, maxValue.float)
+ gtk_adjustment_set_page_size(control.fVAdjust, control.height.float)
+ gtk_widget_set_size_request(control.fVScrollbar, 0, size.cint)
+ gtk_layout_move(control.fHandle, control.fVScrollbar, (control.width - fScrollbarSize).cint, 0)
+ gtk_widget_show(control.fVScrollbar)
+ # Ensure that scroll pos is within range:
+ control.fYScrollPos = max(min(control.fYScrollPos, maxValue), 0)
+ else:
+ gtk_widget_hide(control.fVScrollbar)
+ control.fYScrollPos = 0
+
+ # Update dead corner:
+ if control.fXScrollEnabled and control.fYScrollEnabled:
+ gtk_layout_move(control.fHandle, control.fDeadCornerHandle, (control.width - fScrollbarSize).cint, (control.height - fScrollbarSize).cint)
+ gtk_widget_show(control.fDeadCornerHandle)
+ else:
+ gtk_widget_hide(control.fDeadCornerHandle)
+
+
+method setSize(control: ControlImpl, width, height: int) =
+ if width == control.fWidth and height == control.fHeight:
+ return
+ procCall control.Control.setSize(width, height)
+ gtk_widget_set_size_request(control.fHandle, width.cint, height.cint)
+ pUpdateScrollBar(control)
+
+method setPosition(control: ControlImpl, x, y: int) =
+ procCall control.Control.setPosition(x, y)
+ if control.fParentControl != nil:
+ gtk_fixed_move(cast[ContainerImpl](control.fParentControl).fInnerHandle, control.fHandle, x.cint, y.cint)
+ # gtk_layout_move(cast[ContainerImpl](control.fParentControl).fHandle, control.fHandle, x.cint, y.cint)
+
+method forceRedraw(control: ControlImpl) = gtk_widget_queue_draw(control.fHandle)
+
+# proc removeWidgetInternal(container: WidgetContainer, widget: Widget) = gtk_container_remove(container.innerHandle, widget.handle)
+
+method setFontFamily(control: ControlImpl, fontFamily: string) =
+ procCall control.Control.setFontFamily(fontFamily)
+ control.pUpdateFont()
+
+method setFontSize(control: ControlImpl, fontSize: int) =
+ procCall control.Control.setFontSize(fontSize)
+ control.pUpdateFont()
+
+method setTextColor(control: ControlImpl, color: Color) =
+ procCall control.Control.setTextColor(color)
+ control.pUpdateFont()
+
+method `setBackgroundColor=`(control: ControlImpl, color: Color) =
+ procCall control.Control.setBackgroundColor(color)
+ var rgba: GdkRGBA
+ color.pColorToGdkRGBA(rgba)
+ gtk_widget_override_background_color(control.fHandle, GTK_STATE_FLAG_NORMAL, rgba)
+ # TODO: check why it has no effect
+
+method getTextLineWidth(control: ControlImpl, text: string): int =
+ var layout = gtk_widget_create_pango_layout(control.fHandle, text)
+ var width: cint = 0
+ var height: cint = 0
+ pango_layout_get_pixel_size(layout, width, height)
+ result = width
+
+method getTextLineHeight(control: ControlImpl): int =
+ var layout = gtk_widget_create_pango_layout(control.fHandle, "a")
+ var width: cint = 0
+ var height: cint = 0
+ pango_layout_get_pixel_size(layout, width, height)
+ result = height
+
+discard """ method `xScrollPos=`(control: ControlImpl, xScrollPos: int) =
+ procCall control.Control.`xScrollPos=`(xScrollPos)
+ control.pUpdateScrollBar()
+
+method `yScrollPos=`(control: ControlImpl, yScrollPos: int) =
+ procCall control.Control.`yScrollPos=`(yScrollPos)
+ control.pUpdateScrollBar() """
+
+method `scrollableWidth=`(control: ControlImpl, scrollableWidth: int) =
+ if scrollableWidth == control.fScrollableWidth:
+ return
+ procCall control.Control.`scrollableWidth=`(scrollableWidth)
+ control.pUpdateScrollBar()
+
+method `scrollableHeight=`(control: ControlImpl, scrollableHeight: int) =
+ if scrollableHeight == control.fScrollableHeight:
+ return
+ procCall control.Control.`scrollableHeight=`(scrollableHeight)
+ control.pUpdateScrollBar()
+
+
+# ----------------------------------------------------------------------------------------
+# Container
+# ----------------------------------------------------------------------------------------
+
+proc init(container: ContainerImpl) =
+ container.fHandle = gtk_fixed_new()
+ # ScrollWnd:
+ container.fScrollWndHandle = gtk_scrolled_window_new(nil, nil)
+ gtk_widget_show(container.fScrollWndHandle)
+ gtk_container_add(container.fHandle, container.fScrollWndHandle)
+ # Inner:
+ container.fInnerHandle = gtk_fixed_new()
+ gtk_widget_show(container.fInnerHandle)
+ gtk_container_add(container.fScrollWndHandle, container.fInnerHandle)
+ container.Container.init()
+
+method pUpdateScrollWnd(container: ContainerImpl) =
+ let padding = container.getPadding()
+ let width = container.width - padding.left - padding.right
+ let height = container.height - padding.top - padding.bottom
+ gtk_widget_set_size_request(container.fScrollWndHandle, width.cint, height.cint)
+ gtk_fixed_move(container.fHandle, container.fScrollWndHandle, padding.left.cint, padding.top.cint)
+
+method `frame=`(container: ContainerImpl, frame: Frame) =
+ procCall container.Container.`frame=`(frame)
+ if frame != nil:
+ gtk_container_add(container.fHandle, frame.fHandle)
+ container.pUpdateScrollWnd()
+
+method add(container: ContainerImpl, control: ControlImpl) =
+ gtk_container_add(container.fInnerHandle, control.fHandle)
+ procCall container.Container.add(control)
+
+method paddingLeft(container: ContainerImpl): int = 5 # TODO
+method paddingRight(container: ContainerImpl): int = 5 # TODO
+method paddingTop(container: ContainerImpl): int = 15 # TODO
+method paddingBottom(container: ContainerImpl): int = 5 # TODO
+
+method setInnerSize(container: ContainerImpl, width, height: int) =
+ procCall container.Container.setInnerSize(width, height)
+ gtk_widget_set_size_request(container.fInnerHandle, width.cint, height.cint)
+
+method setSize(container: ContainerImpl, width, height: int) =
+ procCall container.Container.setSize(width, height)
+ container.pUpdateScrollWnd()
+
+method pUpdateScrollBar(container: ContainerImpl) =
+ # Overwrite base method
+ if container.fScrollableWidth == -1 and container.fScrollableHeight == -1:
+ return
+ # echo "container.pUpdateScrollBar"
+
+ var xPolicy: cint = GTK_POLICY_NEVER
+ var yPolicy: cint = GTK_POLICY_NEVER
+ if container.fXScrollEnabled:
+ xPolicy = GTK_POLICY_AUTOMATIC
+ if container.fYScrollEnabled:
+ yPolicy = GTK_POLICY_AUTOMATIC
+ gtk_scrolled_window_set_policy(container.fScrollWndHandle, xPolicy, yPolicy)
+
+
+# ----------------------------------------------------------------------------------------
+# Frame
+# ----------------------------------------------------------------------------------------
+
+proc init(frame: NativeFrame) =
+ frame.fHandle = gtk_frame_new("")
+ frame.Frame.init()
+
+method `text=`(frame: NativeFrame, text: string) =
+ procCall frame.Frame.`text=`(text)
+ gtk_frame_set_label(frame.fHandle, text)
+
+method getPadding(frame: NativeFrame): Spacing =
+ result = procCall frame.Frame.getPadding()
+ result.top = frame.getTextLineHeight() * frame.text.countLines + 2
+
+
+# ----------------------------------------------------------------------------------------
+# Button
+# ----------------------------------------------------------------------------------------
+
+proc init(button: NativeButton) =
+ button.fHandle = gtk_button_new()
+ button.Button.init()
+ # discard g_signal_connect_data(button.fHandle, "clicked", pWidgetClickSignal, cast[pointer](button))
+
+method `text=`(button: NativeButton, text: string) =
+ procCall button.Button.`text=`(text)
+ gtk_button_set_label(button.fHandle, text)
+ # Don't let the button expand:
+ let list = gtk_container_get_children(button.fHandle)
+ if list != nil:
+ gtk_label_set_ellipsize(list.data, PANGO_ELLIPSIZE_END)
+ app.processEvents()
+
+method naturalWidth(button: NativeButton): int =
+ # Override parent method, to make it big enough for the text to fit in.
+ var context = gtk_widget_get_style_context(button.fHandle)
+ var padding: GtkBorder
+ gtk_style_context_get_padding(context, GTK_STATE_FLAG_NORMAL, padding)
+ result = button.getTextLineWidth(button.text) + padding.left + padding.right + 5
+
+method pAddButtonPressEvent(control: NativeButton) =
+ gtk_widget_add_events(control.fHandle, GDK_BUTTON_PRESS_MASK)
+ discard g_signal_connect_data(control.fHandle, "button-press-event", pDefaultControlButtonPressSignal, cast[pointer](control))
+
+
+# ----------------------------------------------------------------------------------------
+# Label
+# ----------------------------------------------------------------------------------------
+
+proc init(label: NativeLabel) =
+ label.fHandle = gtk_label_new("")
+ gtk_label_set_xalign(label.fHandle, 0)
+ gtk_label_set_yalign(label.fHandle, 0.5)
+ gtk_label_set_ellipsize(label.fHandle, PANGO_ELLIPSIZE_END)
+ label.Label.init()
+
+method `text=`(label: NativeLabel, text: string) =
+ procCall label.Label.`text=`(text)
+ gtk_label_set_text(label.fHandle, text)
+ app.processEvents()
+
+method naturalWidth(label: NativeLabel): int = label.getTextLineWidth(label.text) + 10
+# Override parent method, to make it big enough for the text to fit in.
+
+method pAddButtonPressEvent(control: NativeLabel) =
+ gtk_widget_add_events(control.fHandle, GDK_BUTTON_PRESS_MASK)
+ discard g_signal_connect_data(control.fHandle, "button-press-event", pDefaultControlButtonPressSignal, cast[pointer](control))
+
+
+# ----------------------------------------------------------------------------------------
+# TextBox
+# ----------------------------------------------------------------------------------------
+
+proc init(textBox: NativeTextBox) =
+ textBox.fHandle = gtk_entry_new()
+ discard g_signal_connect_data(textBox.fHandle, "changed", pControlChangedSignal, cast[pointer](textBox))
+ textBox.TextBox.init()
+
+method text(textBox: NativeTextBox): string = $gtk_entry_get_text(textBox.fHandle)
+
+method `text=`(textBox: NativeTextBox, text: string) =
+ gtk_entry_set_text(textBox.fHandle, text)
+ app.processEvents()
+
+method naturalHeight(textBox: NativeTextBox): int = textBox.getTextLineHeight() + 12 # add padding
+
+method setSize(textBox: NativeTextBox, width, height: int) =
+ gtk_entry_set_width_chars(textBox.fHandle, 1)
+ procCall textBox.ControlImpl.setSize(width, height)
+
+method pAddButtonPressEvent(control: NativeTextBox) =
+ gtk_widget_add_events(control.fHandle, GDK_BUTTON_PRESS_MASK)
+ discard g_signal_connect_data(control.fHandle, "button-press-event", pDefaultControlButtonPressSignal, cast[pointer](control))
+
+
+# ----------------------------------------------------------------------------------------
+# TextArea
+# ----------------------------------------------------------------------------------------
+
+proc init(textArea: NativeTextArea) =
+ textArea.fHandle = gtk_scrolled_window_new(nil, nil)
+ # gtk_scrolled_window_set_policy(textArea.fHandle, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC)
+ gtk_scrolled_window_set_policy(textArea.fHandle, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC)
+ textArea.fTextViewHandle = gtk_text_view_new()
+ gtk_text_view_set_left_margin(textArea.fTextViewHandle, 5)
+ gtk_text_view_set_right_margin(textArea.fTextViewHandle, 5)
+ gtk_text_view_set_top_margin(textArea.fTextViewHandle, 5)
+ gtk_text_view_set_bottom_margin(textArea.fTextViewHandle, 5)
+ gtk_container_add(textArea.fHandle, textArea.fTextViewHandle)
+ gtk_widget_show(textArea.fTextViewHandle)
+ discard g_signal_connect_data(textArea.fTextViewHandle, "key-press-event", pControlKeyPressSignal, cast[pointer](textArea))
+ textArea.fBufferHandle = gtk_text_view_get_buffer(textArea.fTextViewHandle)
+ discard g_signal_connect_data(textArea.fBufferHandle, "changed", pControlChangedSignal, cast[pointer](textArea))
+ textArea.TextArea.init()
+
+method text(textArea: NativeTextArea): string =
+ var startIter, endIter: GtkTextIter
+ gtk_text_buffer_get_start_iter(textArea.fBufferHandle, startIter)
+ gtk_text_buffer_get_end_iter(textArea.fBufferHandle, endIter)
+ result = $gtk_text_buffer_get_text(textArea.fBufferHandle, startIter, endIter, 1)
+
+method `text=`(textArea: NativeTextArea, text: string) =
+ gtk_text_buffer_set_text(textArea.fBufferHandle, text, text.len.cint)
+ app.processEvents()
+
+method addText(textArea: NativeTextArea, text: string) =
+ # overide base method for better performance and to prevent automatic scrolling to top
+ var iter: GtkTextIter
+ gtk_text_buffer_get_end_iter(textArea.fBufferHandle, iter)
+ gtk_text_buffer_insert(textArea.fBufferHandle, iter, text, text.len.cint)
+ app.processEvents()
+
+method scrollToBottom(textArea: NativeTextArea) =
+ var iter: GtkTextIter
+ gtk_text_buffer_get_end_iter(textArea.fBufferHandle, iter)
+ gtk_text_view_scroll_to_iter(textArea.fTextViewHandle, iter, 0, false, 0, 0)
+ app.processEvents()
+
+method pAddButtonPressEvent(control: NativeTextArea) =
+ gtk_widget_add_events(control.fHandle, GDK_BUTTON_PRESS_MASK)
+ discard g_signal_connect_data(control.fHandle, "button-press-event", pDefaultControlButtonPressSignal, cast[pointer](control))
+
+method `wrap=`(textArea: NativeTextArea, wrap: bool) =
+ procCall textArea.TextArea.`wrap=`(wrap)
+ if wrap:
+ gtk_text_view_set_wrap_mode(textArea.fTextViewHandle, GTK_WRAP_WORD_CHAR)
+ else:
+ gtk_text_view_set_wrap_mode(textArea.fTextViewHandle, GTK_WRAP_NONE)
+
diff --git a/src/gtk3/nigui_platform_types1.nim b/src/gtk3/nigui_platform_types1.nim
new file mode 100755
index 0000000..d0178cd
--- /dev/null
+++ b/src/gtk3/nigui_platform_types1.nim
@@ -0,0 +1,25 @@
+# NiGui - GTK+ 3 platform-specific code - part 1
+
+# This file will be included in "nigui.nim".
+
+type
+ WindowImpl* = ref object of Window
+ fHandle: pointer
+ fInnerHandle: pointer
+
+ ControlImpl* = ref object of Control
+ fHandle: pointer
+ fHScrollbar: pointer
+ fVScrollbar: pointer
+ fHAdjust: pointer
+ fVAdjust: pointer
+ fDeadCornerHandle: pointer
+
+ CanvasImpl* = ref object of Canvas
+ fSurface: pointer
+ fData: cstring
+ fStride: int
+ fCairoContext: pointer
+ fFont: pointer
+
+ ImageImpl* = ref object of Image
diff --git a/src/gtk3/nigui_platform_types2.nim b/src/gtk3/nigui_platform_types2.nim
new file mode 100755
index 0000000..c176ffc
--- /dev/null
+++ b/src/gtk3/nigui_platform_types2.nim
@@ -0,0 +1,20 @@
+# NiGui - GTK+ 3 platform-specific code - part 2
+
+# This file will be included in "nigui.nim".
+
+type
+ ContainerImpl* = ref object of Container
+ fScrollWndHandle: pointer
+ fInnerHandle: pointer
+
+ NativeFrame* = ref object of Frame
+
+ NativeButton* = ref object of Button
+
+ NativeLabel* = ref object of Label
+
+ NativeTextBox* = ref object of TextBox
+
+ NativeTextArea* = ref object of TextArea
+ fTextViewHandle: pointer
+ fBufferHandle: pointer
diff --git a/src/windows/nigui_platform_impl.nim b/src/windows/nigui_platform_impl.nim
new file mode 100755
index 0000000..c4c3010
--- /dev/null
+++ b/src/windows/nigui_platform_impl.nim
@@ -0,0 +1,1278 @@
+# NiGui - Win32 platform-specific code - part 3
+
+# This file will be included in "nigui.nim".
+
+# Idendifiers, which are only used in this file, are marked with a leading "p".
+
+# Imports:
+# math, os, strutils, times are imported by nigui.nim
+import windows
+import tables
+
+
+# ----------------------------------------------------------------------------------------
+# Internal Things
+# ----------------------------------------------------------------------------------------
+
+const pTopLevelWindowClass = "1"
+const pContainerWindowClass = "2"
+const pCustomControlWindowClass = "3"
+
+var pDefaultParentWindow: pointer
+var pKeyState: KeyState
+
+# needed to calculate clicks:
+var pLastMouseButtonDownControl: Control
+var pLastMouseButtonDownControlX: int
+var pLastMouseButtonDownControlY: int
+
+proc pRaiseLastOSError(showAlert = true) =
+ let e = osLastError()
+ raiseError(osErrorMsg(e).strip & " (OS Error Code: " & $e & ")", showAlert)
+
+proc pCheckGdiplusStatus(status: int32, showAlert = true) =
+ if status != 0:
+ if status == 7:
+ pRaiseLastOSError(showAlert)
+ else:
+ raiseError("A GDI+ error occured. (Status: " & $status & ")", showAlert)
+
+proc pColorToRGB32(color: Color): RGB32 =
+ result.red = color.red
+ result.green = color.green
+ result.blue = color.blue
+
+proc pRgb32ToColor(color: RGB32): Color =
+ result.red = color.red
+ result.green = color.green
+ result.blue = color.blue
+
+proc pColorToARGB(color: Color): ARGB =
+ result.red = color.red
+ result.green = color.green
+ result.blue = color.blue
+ result.alpha = color.alpha
+
+proc pUtf8ToUtf16(s: string): string =
+ # result is terminated with 2 null bytes
+ if s.len == 0:
+ return "\0"
+ var characters = MultiByteToWideChar(CP_UTF8, 0, s, s.len.int32, nil, 0) # request number of characters
+ if characters == 0: pRaiseLastOSError()
+ result = newString(characters * 2 + 1)
+ characters = MultiByteToWideChar(CP_UTF8, 0, s, s.len.int32, result, characters.int32) # do the conversion
+ result[characters * 2] = '\0'
+ result[characters * 2 + 1] = '\0'
+ if characters == 0: pRaiseLastOSError()
+
+proc pUtf16ToUtf8(s: string, searchEnd = false): string =
+ if s.len == 0:
+ return ""
+ var characters = s.len div 2
+ if searchEnd:
+ # Search end of utf16 string:
+ var i = 0
+ while i < s.len - 1 and s[i].ord != 0:
+ i.inc(2)
+ characters = i div 2
+ var bytes = WideCharToMultiByte(CP_UTF8, 0, s, characters.int32, nil, 0, nil, nil) # request number of bytes
+ if bytes == 0: pRaiseLastOSError()
+ result = newString(bytes)
+ bytes = WideCharToMultiByte(CP_UTF8, 0, s, characters.int32, result, bytes.int32, nil, nil) # do the conversion
+ if bytes == 0: pRaiseLastOSError()
+
+proc pUnicodeCharToUtf8(unicode: int): string =
+ var widestring = newString(2)
+ widestring[0] = chr(unicode mod 256)
+ widestring[1] = chr(unicode div 256)
+ result = widestring.pUtf16ToUtf8
+
+proc pUtf16ToUnicode(s: string): int = s[0].ord + s[1].ord * 256
+
+proc pShowWindow(hWnd: pointer, nCmdShow: int32) = discard ShowWindow(hWnd, nCmdShow)
+
+proc pSetWindowLong(hWnd: pointer, nIndex, dwNewLong: int32) =
+ let result = SetWindowLongA(hWnd, nIndex, dwNewLong)
+ if result == 0: pRaiseLastOSError()
+
+proc pDestroyWindow(hWnd: pointer) =
+ let result = DestroyWindow(hWnd)
+ if not result: pRaiseLastOSError()
+
+proc pSetParent(hWndChild, hWndNewParent: pointer) =
+ let result = SetParent(hWndChild, hWndNewParent)
+ if result == nil: pRaiseLastOSError()
+
+proc pSetWindowText(hWnd: pointer, s: string) =
+ let result = SetWindowTextW(hWnd, s.pUtf8ToUtf16)
+ if not result: pRaiseLastOSError()
+
+proc pGetWindowText(hWnd: pointer): string =
+ let characters = GetWindowTextLengthW(hWnd)
+ result = newString(characters * 2)
+ var res = GetWindowTextW(hWnd, result, characters * 2 + 1)
+ if res != characters: pRaiseLastOSError()
+ result = result.pUtf16ToUtf8
+
+proc pSetWindowPos(wnd: pointer, x, y, cx, cy: int, uFlags: int32 = 0) =
+ var result = SetWindowPos(wnd, nil, x.int32, y.int32, cx.int32, cy.int32, uFlags)
+ if not result: pRaiseLastOSError()
+
+proc pGetClientRect(wnd: pointer): Rect =
+ if not GetClientRect(wnd, result): pRaiseLastOSError()
+
+proc pGetWindowRect(wnd: pointer): Rect =
+ if not GetWindowRect(wnd, result): pRaiseLastOSError()
+
+proc pCreateWindowEx(dwExStyle: int32, lpClassName, lpWindowName: cstring, dwStyle: int32, x, y, nWidth, nHeight: int, hWndParent, hMenu, hInstance, lpParam: pointer): pointer =
+ result = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)
+ if result == nil: pRaiseLastOSError()
+
+# proc pGetStockObject(fnObject: int32): pointer =
+ # result = GetStockObject(fnObject)
+ # if result == nil: pRaiseLastOSError()
+
+proc pCreateWindowExWithUserdata(lpClassName: cstring, dwStyle, dwExStyle: int32, hWndParent, userdata: pointer = nil): pointer =
+ result = pCreateWindowEx(dwExStyle, lpClassName, nil, dwStyle, 0, 0, 0, 0, hWndParent, nil, nil, nil)
+ if userdata != nil:
+ discard SetWindowLongPtrW(result, GWLP_USERDATA, userdata)
+ # Set default font:
+ # discard SendMessageA(result, WM_SETFONT, pGetStockObject(DEFAULT_GUI_FONT), cast[pointer](true))
+ # Set window proc:
+ # discard SetWindowLongPtrW(result, GWLP_WNDPROC, pCommonWndProc)
+
+proc pEnableVisualStyles() =
+ # Without this, controls have style of Windows 95
+ const MaxLength = 500
+ var dir = newString(MaxLength)
+ if GetSystemDirectoryA(dir[0].addr, MaxLength) == 0: pRaiseLastOSError()
+ var actCtx: ActCtx
+ actCtx.cbSize = ActCtx.sizeof.int32
+ actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID or ACTCTX_FLAG_SET_PROCESS_DEFAULT or ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID
+ actCtx.lpSource = "shell32.dll"
+ actCtx.wProcessorArchitecture = 0
+ actCtx.wLangId = 0
+ actCtx.lpAssemblyDirectory = dir
+ actCtx.lpResourceName = cast[cstring](124)
+ actCtx.lpApplicationName = nil
+ actCtx.hModule = nil
+ var context = CreateActCtxA(actCtx.addr)
+ if context == INVALID_HANDLE_VALUE: pRaiseLastOSError()
+ # has no effect:
+ # var ulpActivationCookie = false
+ # if not ActivateActCtx(context, ulpActivationCookie.addr): pRaiseLastOSError()
+
+proc pRegisterWindowClass(className: cstring, wndProc: pointer, style: int32 = 0) =
+ var class: WndClassEx
+ class.cbSize = WndClassEx.sizeof.int32
+ class.lpszClassName = className
+ class.lpfnWndProc = wndProc
+ class.style = style
+ class.cbClsExtra = 0
+ class.cbWndExtra = 0
+ class.hInstance = nil
+ class.hIcon = nil
+ class.hCursor = LoadCursorA(nil, cast[cstring](IDC_ARROW))
+ class.hbrBackground = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)) # default background
+ class.lpszMenuName = nil
+ class.hIconSm = nil
+ if RegisterClassExA(class) == 0: pRaiseLastOSError()
+
+proc pCommonWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer =
+ case uMsg
+ of WM_COMMAND:
+ if wParam.hiWord == EN_CHANGE:
+ let control = cast[Control](GetWindowLongPtrW(lParam, GWLP_USERDATA))
+ var evt = new TextChangeEvent
+ control.handleTextChangeEvent(evt)
+ of WM_CTLCOLOREDIT:
+ let control = cast[Control](GetWindowLongPtrW(lParam, GWLP_USERDATA))
+ discard SetTextColor(wParam, control.textColor.pColorToRGB32())
+ # discard SetBkColor(wParam, control.backgroundColor.pColorToRGB32())
+ # does not cover complete background
+ return GetCurrentObject(wParam, OBJ_BRUSH)
+ else:
+ discard
+ result = DefWindowProcA(hWnd, uMsg, wParam, lParam)
+
+proc pKeyvalToKey(keyval: int): Key =
+ case keyval
+ of 48: Key_Number0
+ of 49: Key_Number1
+ of 50: Key_Number2
+ of 51: Key_Number3
+ of 52: Key_Number4
+ of 53: Key_Number5
+ of 54: Key_Number6
+ of 55: Key_Number7
+ of 56: Key_Number8
+ of 57: Key_Number9
+ of 65: Key_A
+ of 66: Key_B
+ of 67: Key_C
+ of 68: Key_D
+ of 69: Key_E
+ of 70: Key_F
+ of 71: Key_G
+ of 72: Key_H
+ of 73: Key_I
+ of 74: Key_J
+ of 75: Key_K
+ of 76: Key_L
+ of 77: Key_M
+ of 78: Key_N
+ of 79: Key_O
+ of 80: Key_P
+ of 81: Key_Q
+ of 82: Key_R
+ of 83: Key_S
+ of 84: Key_T
+ of 85: Key_U
+ of 86: Key_V
+ of 87: Key_W
+ of 88: Key_X
+ of 89: Key_Y
+ of 90: Key_Z
+ of 32: Key_Space
+ of 9: Key_Tab
+ of 13: Key_Return
+ of 27: Key_Escape
+ of 45: Key_Insert
+ of 46: Key_Delete
+ of 8: Key_Backspace
+ of 37: Key_Left
+ of 38: Key_Up
+ of 39: Key_Right
+ of 40: Key_Down
+ of 35: Key_End
+ of 36: Key_Home
+ of 33: Key_PageUp
+ of 34: Key_PageDown
+ else: Key_None
+
+proc pHandleWMKEYDOWN(window: Window, control: Control, wParam, lParam: pointer) =
+ var windowEvent = new WindowKeyEvent
+ windowEvent.window = window
+ windowEvent.key = pKeyvalToKey(cast[int](wParam))
+ if windowEvent.key == Key_None:
+ echo "Unkown key value: ", cast[int](wParam)
+ return
+ if not GetKeyboardState(pKeyState): pRaiseLastOSError()
+ var widestring = newString(2)
+ let scancode = int32((cast[int](lParam) shr 8) and 0xFFFFFF00)
+ let ret = ToUnicode(cast[int](wParam).int32, scancode, pKeyState, widestring, 1, 0)
+ if ret == 1:
+ windowEvent.unicode = widestring.pUtf16ToUnicode
+ windowEvent.character = windowEvent.unicode.pUnicodeCharToUtf8
+ else:
+ windowEvent.character = ""
+ window.handleKeyDownEvent(windowEvent)
+
+
+ # var windowEvent = new WindowKeyEvent
+ # windowEvent.window = window
+ # windowEvent.character = $chr(cast[int](wParam))
+ # windowEvent.key = pKeyvalToKey(cast[int](wParam))
+ # window.handleKeyDownEvent(windowEvent)
+
+ if control != nil:
+ var controlEvent = new ControlKeyEvent
+ controlEvent.control = control
+ controlEvent.key = windowEvent.key
+ controlEvent.unicode = windowEvent.unicode
+ controlEvent.character = windowEvent.character
+ control.handleKeyDownEvent(controlEvent)
+ # if controlEvent.cancel:
+ # return nil # key is still inserted in text area
+
+
+proc pWindowWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} =
+ case uMsg
+ of WM_CLOSE:
+ let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ window.dispose()
+ return cast[pointer](true) # keeps the window open, else the window will be destroyed
+ of WM_SIZE:
+ let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if window != nil:
+ var rect = pGetWindowRect(window.fHandle)
+ window.width = rect.right - rect.left
+ window.height = rect.bottom - rect.top
+ rect = pGetClientRect(window.fHandle)
+ window.fClientWidth = rect.right - rect.left
+ window.fClientHeight = rect.bottom - rect.top
+ window.triggerRelayout()
+ of WM_MOVE:
+ let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if window != nil:
+ var rect = pGetWindowRect(window.fHandle)
+ window.fX = rect.left
+ window.fY = rect.top
+ # echo "WM_MOVE: " & $rect.left & ", " & $rect.top
+ of WM_SETFOCUS:
+ discard
+ #echo "window WM_SETFOCUS"
+ # not called?
+ of WM_DROPFILES:
+ let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ var files: seq[string] = @[]
+ let count = DragQueryFileW(wParam, 0xFFFFFFFF.uint32, nil, 0)
+ for i in 0..count - 1:
+ let characters = DragQueryFileW(wParam, i.uint32 , nil, 0)
+ var filename = newString(characters * 2)
+ discard DragQueryFileW(wParam, i.uint32, filename, characters + 1)
+ files.add(filename.pUtf16ToUtf8)
+ DragFinish(wParam)
+ var event = new DropFilesEvent
+ event.window = window
+ event.files = files
+ window.handleDropFilesEvent(event)
+ # of WM_CHAR:
+ # not triggered for all key
+ # echo "window WM_CHAR: "
+ # let window = cast[Window](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ # if window != nil:
+ # var unicode = cast[int](wParam)
+ # var event = new WindowKeyEvent
+ # event.window = window
+ # event.unicode = unicode
+ # event.character = unicode.pUnicodeCharToUtf8
+ # echo event.character[0].ord
+ # window.handleKeyDownEvent(event)
+ of WM_KEYDOWN:
+ # echo "window WM_KEYDOWN"
+
+ # if (cast[int](lParam) and 0x40000000) != 0x40000000:
+ # echo "window WM_KEYDOWN first"
+ # else:
+ # echo "window WM_KEYDOWN"
+
+ # echo int((cast[int](lParam) shr 8) and 0xFFFFFF00)
+
+ let window = cast[Window](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if window != nil:
+ pHandleWMKEYDOWN(window, nil, wParam, lParam)
+ else:
+ discard
+ result = pCommonWndProc(hWnd, uMsg, wParam, lParam)
+
+proc pContainerWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.}
+
+proc pCommonControlWndProc(origWndProc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer
+
+proc pCustomControlWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.}
+
+proc pInitGdiplus() =
+ var input: GdiplusStartupInput
+ input.GdiplusVersion = 1
+ var gidplus: pointer = nil
+ pCheckGdiplusStatus(GdiplusStartup(gidplus, input, nil))
+
+proc pGetTextSize(hDC, font: pointer, text: string): Size =
+ let wideText = text.pUtf8ToUtf16
+ discard SelectObject(hdc, font)
+ discard GetTextExtentPoint32W(hdc, wideText, (wideText.len div 2).int32, result)
+
+
+# ----------------------------------------------------------------------------------------
+# App Procedures
+# ----------------------------------------------------------------------------------------
+
+proc init(app: App) =
+ pInitGdiplus()
+ pEnableVisualStyles()
+ pRegisterWindowClass(pTopLevelWindowClass, pWindowWndProc)
+ pRegisterWindowClass(pCustomControlWindowClass, pCustomControlWndProc, CS_HREDRAW or CS_VREDRAW)
+ pRegisterWindowClass(pContainerWindowClass, pContainerWndProc)
+ pDefaultParentWindow = pCreateWindowEx(0, pTopLevelWindowClass, nil, 0, 0, 0, 0, 0, nil, nil, nil, nil)
+ app.defaultTextColor = GetSysColor(COLOR_WINDOWTEXT).pRgb32ToColor()
+ app.defaultBackgroundColor = GetSysColor(COLOR_BTNFACE).pRgb32ToColor()
+ app.defaultFontFamily = "Arial"
+ fScrollbarSize = GetSystemMetrics(SM_CXVSCROLL)
+
+proc runMainLoop() =
+ var msg: Msg
+ while GetMessageA(msg.addr, nil, 0, 0):
+ discard TranslateMessage(msg.addr)
+ discard DispatchMessageA(msg.addr)
+
+proc processEvents(app: App) =
+ var msg: Msg
+ while PeekMessageA(msg.addr, nil, 0, 0, PM_REMOVE):
+ discard TranslateMessage(msg.addr)
+ discard DispatchMessageA(msg.addr)
+
+
+# ----------------------------------------------------------------------------------------
+# Dialogs
+# ----------------------------------------------------------------------------------------
+
+proc alert(window: Window, message: string, title = "Message") =
+ var hWnd: pointer
+ if window != nil:
+ hWnd = cast[WindowImpl](window).fHandle
+ MessageBoxW(hWnd, message.pUtf8ToUtf16, title.pUtf8ToUtf16, 0)
+
+method run*(dialog: OpenFileDialog) =
+ const maxCharacters = 5000
+ dialog.files = @[]
+ var ofn: OpenFileName
+ ofn.lStructSize = OpenFileName.sizeOf.int32
+ ofn.nMaxFile = maxCharacters
+ ofn.lpstrInitialDir = getCurrentDir().pUtf8ToUtf16()
+ ofn.Flags = OFN_FILEMUSTEXIST
+ if dialog.multiple:
+ ofn.Flags = ofn.Flags or OFN_ALLOWMULTISELECT or OFN_EXPLORER
+ var s = newString(maxCharacters * 2)
+ ofn.lpstrFile = s
+ let ret = GetOpenFileNameW(ofn)
+ if ret:
+ var dirOrFirstFile: string
+ # Split selected file names:
+ while s[0].ord != 0:
+ var i = 0
+ while i < s.len - 1 and s[i].ord != 0:
+ i.inc(2)
+ let filename = s.substr(0, i - 1).pUtf16ToUtf8()
+ if dirOrFirstFile == nil:
+ dirOrFirstFile = filename
+ else:
+ dialog.files.add(dirOrFirstFile / filename)
+ s = s.substr(i + 2)
+ if dialog.files.len == 0:
+ dialog.files.add(dirOrFirstFile)
+ else:
+ let e = CommDlgExtendedError()
+ if e != 0:
+ raiseError("CommDlg Error Code: " & $e)
+
+method run(dialog: SaveFileDialog) =
+ const maxCharacters = 500
+ var ofn: OpenFileName
+ ofn.lStructSize = OpenFileName.sizeOf.int32
+ ofn.nMaxFile = maxCharacters
+ ofn.lpstrInitialDir = getCurrentDir().pUtf8ToUtf16()
+ if dialog.defaultExtension.len > 0:
+ ofn.lpstrDefExt = pUtf8ToUtf16(dialog.defaultExtension)
+ ofn.lpstrFilter = pUtf8ToUtf16(dialog.defaultExtension & "\0*." & dialog.defaultExtension & "\0All files\0*.*")
+ ofn.Flags = OFN_OVERWRITEPROMPT
+ var s = newString(maxCharacters * 2)
+ if dialog.defaultName.len > 0:
+ let temp = pUtf8ToUtf16(dialog.defaultName)
+ for i in 0..temp.len:
+ s[i] = temp[i]
+ ofn.lpstrFile = s
+ let ret = GetSaveFileNameW(ofn)
+ if ret:
+ dialog.file = pUtf16ToUtf8(s, true)
+ else:
+ dialog.file = ""
+ let e = CommDlgExtendedError()
+ if e != 0:
+ raiseError("CommDlg Error Code: " & $e)
+
+
+# ----------------------------------------------------------------------------------------
+# Timers
+# ----------------------------------------------------------------------------------------
+
+type TimerEntry = object
+ timerProc: TimerProc
+ data: pointer
+
+var pTimers = initTable[int64, TimerEntry]()
+
+proc pTimerFunction(hwnd: pointer, uMsg: int32, idEvent: pointer, dwTime: int32) {.cdecl.} =
+ discard KillTimer(hwnd, idEvent)
+ let timerEntry = pTimers.getOrDefault(cast[int](idEvent))
+ var event = new TimerEvent
+ event.timer = cast[Timer](idEvent)
+ event.data = timerEntry.data
+ timerEntry.timerProc(event)
+ pTimers.del(cast[int](idEvent))
+
+proc pRepeatingTimerFunction(hwnd: pointer, uMsg: int32, idEvent: pointer, dwTime: int32) {.cdecl.} =
+ let timerEntry = pTimers.getOrDefault(cast[int](idEvent))
+ var event = new TimerEvent
+ event.timer = cast[Timer](idEvent)
+ event.data = timerEntry.data
+ timerEntry.timerProc(event)
+
+proc startTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer =
+ result = cast[Timer](SetTimer(nil, nil, milliSeconds.int32, pTimerFunction))
+ var timerEntry: TimerEntry
+ timerEntry.timerProc = timerProc
+ timerEntry.data = data
+ pTimers[cast[int](result)] = timerEntry
+
+proc startRepeatingTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer =
+ result = cast[Timer](SetTimer(nil, nil, milliSeconds.int32, pRepeatingTimerFunction))
+ var timerEntry: TimerEntry
+ timerEntry.timerProc = timerProc
+ timerEntry.data = data
+ pTimers[cast[int](result)] = timerEntry
+
+proc stop(timer: var Timer) =
+ if cast[int](timer) != inactiveTimer:
+ let timerEntry = pTimers.getOrDefault(cast[int](timer))
+ pTimers.del(cast[int](timer))
+ discard KillTimer(nil, cast[pointer](timer))
+ timer = cast[Timer](inactiveTimer)
+
+
+# ----------------------------------------------------------------------------------------
+# Canvas
+# ----------------------------------------------------------------------------------------
+
+proc pUpdateFont(canvas: Canvas) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fFont == nil:
+ var fontFamily: pointer
+ pCheckGdiplusStatus(GdipCreateFontFamilyFromName(canvas.fontFamily.pUtf8ToUtf16(), nil, fontFamily))
+ pCheckGdiplusStatus(GdipCreateFont(fontFamily, canvas.fontSize.float, 0, UnitPixel, canvasImpl.fFont))
+ pCheckGdiplusStatus(GdipDeleteFontFamily(fontFamily))
+
+proc pDeleteFont(canvas: CanvasImpl) =
+ if canvas.fFont != nil:
+ pCheckGdiplusStatus(GdipDeleteFont(canvas.fFont))
+ canvas.fFont = nil
+
+proc pDeleteFontBrush(canvas: CanvasImpl) =
+ if canvas.fFontBrush != nil:
+ pCheckGdiplusStatus(GdipDeleteBrush(canvas.fFontBrush))
+ canvas.fFontBrush = nil
+
+proc pDeleteAreaBrush(canvas: CanvasImpl) =
+ if canvas.fAreaBrush != nil:
+ pCheckGdiplusStatus(GdipDeleteBrush(canvas.fAreaBrush))
+ canvas.fAreaBrush = nil
+
+proc pDeleteLinePen(canvas: CanvasImpl) =
+ if canvas.fLinePen != nil:
+ pCheckGdiplusStatus(GdipDeletePen(canvas.fLinePen))
+ canvas.fLinePen = nil
+
+method destroy(canvas: CanvasImpl) =
+ procCall canvas.Canvas.destroy()
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fBitmap == nil:
+ pDeleteFont(canvas)
+ pDeleteFontBrush(canvas)
+ pDeleteLinePen(canvas)
+ pDeleteAreaBrush(canvas)
+ if canvas.fGraphics != nil:
+ pCheckGdiplusStatus(GdipDeleteGraphics(canvas.fGraphics))
+
+method drawText(canvas: Canvas, text: string, x, y = 0) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fGraphics == nil:
+ raiseError("Canvas is not in drawing state.")
+ canvas.pUpdateFont()
+ if canvasImpl.fFontBrush == nil:
+ pCheckGdiplusStatus(GdipCreateSolidFill(canvas.textColor.pColorToARGB(), canvasImpl.fFontBrush))
+ var rect: RectF
+ rect.x = x.float
+ rect.y = y.float
+ pCheckGdiplusStatus(GdipDrawString(canvasImpl.fGraphics, text.pUtf8ToUtf16(), -1, canvasImpl.fFont, rect, nil, canvasImpl.fFontBrush))
+
+method drawLine(canvas: Canvas, x1, y1, x2, y2: int) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fGraphics == nil:
+ raiseError("Canvas is not in drawing state.")
+ if canvasImpl.fLinePen == nil:
+ pCheckGdiplusStatus(GdipCreatePen1(canvas.lineColor.pColorToARGB(), 1, UnitPixel, canvasImpl.fLinePen))
+ pCheckGdiplusStatus(GdipDrawLineI(canvasImpl.fGraphics, canvasImpl.fLinePen, x1.int32, y1.int32, x2.int32, y2.int32))
+
+method drawRectArea(canvas: Canvas, x, y, width, height: int) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fGraphics == nil:
+ raiseError("Canvas is not in drawing state.")
+ if canvasImpl.fAreaBrush == nil:
+ pCheckGdiplusStatus(GdipCreateSolidFill(canvas.areaColor.pColorToARGB(), canvasImpl.fAreaBrush))
+ pCheckGdiplusStatus(GdipFillRectangleI(canvasImpl.fGraphics, canvasImpl.fAreaBrush, x.int32, y.int32, width.int32, height.int32))
+
+method drawRectOutline(canvas: Canvas, x, y, width, height: int) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fGraphics == nil:
+ raiseError("Canvas is not in drawing state.")
+ var pen: pointer
+ pCheckGdiplusStatus(GdipCreatePen1(canvas.lineColor.pColorToARGB(), 1, UnitPixel, pen))
+ pCheckGdiplusStatus(GdipDrawRectangleI(canvasImpl.fGraphics, pen, x.int32, y.int32, width.int32, height.int32))
+
+method drawImage(canvas: Canvas, image: Image, x, y = 0, width, height = -1) =
+ var drawWith = image.width
+ var drawHeight = image.height
+ if width != -1:
+ drawWith = width
+ drawHeight = int(drawHeight * drawWith / image.width)
+ if height != -1:
+ drawHeight = height
+ let canvasImpl = cast[CanvasImpl](canvas)
+ let imageCanvas = cast[CanvasImpl](image.canvas)
+ if canvasImpl.fGraphics == nil:
+ raiseError("Canvas is not in drawing state.")
+ pCheckGdiplusStatus(GdipDrawImageRectI(canvasImpl.fGraphics, imageCanvas.fBitmap, x.int32, y.int32, drawWith.int32, drawHeight.int32))
+
+method setPixel(canvas: Canvas, x, y: int, color: Color) =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ if canvasImpl.fBitmap == nil:
+ if canvasImpl.fDC == nil:
+ raiseError("Canvas is not in drawing state.")
+ discard SetPixel(canvasImpl.fDC, x.int32, y.int32, color.pColorToRGB32)
+ else:
+ let imageCanvas = cast[CanvasImpl](canvas)
+ pCheckGdiplusStatus(GdipBitmapSetPixel(imageCanvas.fBitmap, x.int32, y.int32, color.pColorToARGB()))
+
+method `fontFamily=`(canvas: CanvasImpl, fontFamily: string) =
+ procCall canvas.Canvas.`fontFamily=`(fontFamily)
+ canvas.pDeleteFont()
+
+method `fontSize=`(canvas: CanvasImpl, fontSize: int) =
+ procCall canvas.Canvas.`fontSize=`(fontSize)
+ canvas.pDeleteFont()
+
+method `textColor=`(canvas: CanvasImpl, color: Color) =
+ procCall canvas.Canvas.`textColor=`(color)
+ canvas.pDeleteFontBrush()
+
+method `lineColor=`(canvas: CanvasImpl, color: Color) =
+ procCall canvas.Canvas.`lineColor=`(color)
+ pDeleteLinePen(canvas)
+
+method `areaColor=`(canvas: CanvasImpl, color: Color) =
+ procCall canvas.Canvas.`areaColor=`(color)
+ pDeleteAreaBrush(canvas)
+
+proc pGetTextSize(canvas: Canvas, text: string): Size =
+ let canvasImpl = cast[CanvasImpl](canvas)
+ canvas.pUpdateFont()
+ var rect: RectF
+ var boundingBox: RectF
+ pCheckGdiplusStatus(GdipMeasureString(canvasImpl.fGraphics, text.pUtf8ToUtf16(), -1, canvasImpl.fFont, rect, nil, boundingBox, nil, nil))
+ result.cx = boundingBox.width.int32
+ result.cy = boundingBox.height.int32
+
+method getTextLineWidth(canvas: CanvasImpl, text: string): int = canvas.pGetTextSize(text).cx
+
+method getTextLineHeight(canvas: CanvasImpl): int = canvas.pGetTextSize("a").cy
+
+
+# ----------------------------------------------------------------------------------------
+# Image
+# ----------------------------------------------------------------------------------------
+
+method resize(image: Image, width, height: int) =
+ let canvas = cast[CanvasImpl](image.canvas)
+ if canvas.fBitmap != nil:
+ pCheckGdiplusStatus(GdipDisposeImage(canvas.fBitmap), false)
+ pCheckGdiplusStatus(GdipDeleteGraphics(canvas.fGraphics))
+ canvas.fBitmap = nil
+ canvas.fGraphics = nil
+ var dc = CreateCompatibleDC(nil)
+ pCheckGdiplusStatus(GdipCreateFromHDC(dc, canvas.fGraphics))
+ pCheckGdiplusStatus(GdipCreateBitmapFromGraphics(width.int32, height.int32, canvas.fGraphics, canvas.fBitmap))
+ pCheckGdiplusStatus(GdipGetImageGraphicsContext(canvas.fBitmap, canvas.fGraphics)) # it's a new Graphic
+ image.canvas.fWidth = width
+ image.canvas.fHeight = height
+
+method loadFromFile(image: Image, filePath: string) =
+ let canvas = cast[CanvasImpl](image.canvas)
+ if canvas.fBitmap != nil:
+ pCheckGdiplusStatus(GdipDisposeImage(canvas.fBitmap), false)
+ pCheckGdiplusStatus(GdipDeleteGraphics(canvas.fGraphics))
+ canvas.fBitmap = nil
+ canvas.fGraphics = nil
+ pCheckGdiplusStatus(GdipCreateBitmapFromFile(filePath.pUtf8ToUtf16(), canvas.fBitmap), false)
+ pCheckGdiplusStatus(GdipGetImageGraphicsContext(canvas.fBitmap, canvas.fGraphics))
+ var width, height: int32
+ pCheckGdiplusStatus(GdipGetImageWidth(canvas.fBitmap, width))
+ pCheckGdiplusStatus(GdipGetImageHeight(canvas.fBitmap, height))
+ image.canvas.fWidth = width
+ image.canvas.fHeight = height
+
+method saveToPngFile(image: Image, filePath: string) =
+ let canvas = cast[CanvasImpl](image.canvas)
+ var clsidEncoder: GUID
+ clsidEncoder.Data1 = 0x557cf406
+ clsidEncoder.Data2 = 0x11d31a04
+ clsidEncoder.Data3 = 0x0000739a
+ clsidEncoder.Data4 = 0x2ef31ef8
+ pCheckGdiplusStatus(GdipSaveImageToFile(canvas.fBitmap, filePath.pUtf8ToUtf16(), clsidEncoder.addr, nil), false)
+
+method saveToJpegFile(image: Image, filePath: string, quality = 80) =
+ let canvas = cast[CanvasImpl](image.canvas)
+ var clsidEncoder: GUID
+ clsidEncoder.Data1 = 0x557cf401
+ clsidEncoder.Data2 = 0x11d31a04
+ clsidEncoder.Data3 = 0x0000739a
+ clsidEncoder.Data4 = 0x2ef31ef8
+ # TODO: pass quality
+ pCheckGdiplusStatus(GdipSaveImageToFile(canvas.fBitmap, filePath.pUtf8ToUtf16(), clsidEncoder.addr, nil), false)
+
+
+# ----------------------------------------------------------------------------------------
+# Window
+# ----------------------------------------------------------------------------------------
+
+proc init(window: WindowImpl) =
+ var dwStyle: int32 = WS_OVERLAPPEDWINDOW
+ window.fHandle = pCreateWindowExWithUserdata(pTopLevelWindowClass, dwStyle, 0, nil, cast[pointer](window))
+ DragAcceptFiles(window.fHandle, true)
+ window.Window.init()
+
+method destroy(window: WindowImpl) =
+ if window.fModalParent != nil:
+ discard EnableWindow(window.fModalParent.fHandle, true)
+ procCall window.Window.destroy()
+ pDestroyWindow(window.fHandle)
+ window.fHandle = nil
+
+method `visible=`(window: WindowImpl, visible: bool) =
+ procCall window.Window.`visible=`(visible)
+ if visible:
+ pShowWindow(window.fHandle, SW_SHOW)
+ else:
+ pShowWindow(window.fHandle, SW_HIDE)
+
+method showModal(window, parent: WindowImpl) =
+ # Set window owner, to hide it from the taskbar
+ discard SetWindowLongPtrW(window.fHandle, GWL_HWNDPARENT, parent.fHandle)
+
+ # Hide minimize and maximize buttons:
+ pSetWindowLong(window.fHandle, GWL_STYLE, WS_CAPTION or WS_THICKFRAME or WS_SYSMENU)
+ # pSetWindowLong(window.fHandle, GWL_EXSTYLE, WS_EX_TOOLWINDOW) # does not look good
+
+ window.fModalParent = parent
+ window.visible = true
+ discard EnableWindow(parent.fHandle, false)
+
+proc pUpdatePosition(window: WindowImpl) =
+ pSetWindowPos(window.fHandle, window.x, window.y, -1, -1, SWP_NOSIZE)
+ # discard MoveWindow(window.fHandle, window.x.int32, window.y.int32, window.width.int32, window.height.int32, false)
+ # no difference
+
+proc pUpdateSize(window: WindowImpl) = pSetWindowPos(window.fHandle, -1, -1, window.width, window.height, SWP_NOMOVE)
+
+method `x=`(window: WindowImpl, x: int) =
+ procCall window.Window.`x=`(x)
+ window.pUpdatePosition()
+
+method `y=`(window: WindowImpl, y: int) =
+ procCall window.Window.`y=`(y)
+ window.pUpdatePosition()
+
+method centerOnScreen(window: WindowImpl) =
+ let desktop = GetDesktopWindow()
+ var rect: Rect
+ discard SystemParametersInfoA(SPI_GETWORKAREA, 0, rect.addr, 0)
+ window.fX = rect.left + (rect.right - window.width) div 2
+ window.fY = rect.top + (rect.bottom - window.height) div 2
+ window.pUpdatePosition()
+
+ # TODO: regard multiple monitors
+ # var m = MonitorFromRect(rect, 0)
+ # var mi: MonitorInfo
+ # discard GetMonitorInfoA(m, mi)
+ # echo "GetMonitorInfoA: " & $mi.rcMonitor.left
+ # echo "GetMonitorInfoA: " & $mi.rcWork.left
+
+method `width=`*(window: WindowImpl, width: int) =
+ procCall window.Window.`width=`(width)
+ window.pUpdateSize()
+
+method `height=`*(window: WindowImpl, height: int) =
+ procCall window.Window.`height=`(height)
+ window.pUpdateSize()
+
+method `title=`(window: WindowImpl, title: string) =
+ procCall window.Window.`title=`(title)
+ pSetWindowText(window.fHandle, window.title)
+
+method `control=`(window: WindowImpl, control: ControlImpl) =
+ if window.control != nil:
+ pSetParent(cast[ControlImpl](window.control).fHandle, pDefaultParentWindow)
+ window.control.fParentWindow = nil
+ procCall window.Window.`control=`(control)
+ pSetParent(control.fHandle, window.fHandle)
+
+method `iconPath=`(window: WindowImpl, iconPath: string) =
+ procCall window.Window.`iconPath=`(iconPath)
+ var bitmap: pointer
+ pCheckGdiplusStatus(GdipCreateBitmapFromFile(iconPath.pUtf8ToUtf16(), bitmap))
+ var icon: pointer
+ pCheckGdiplusStatus(GdipGetHicon(bitmap, icon))
+ discard SendMessageA(window.fHandle, WM_SETICON, cast[pointer](ICON_BIG), icon)
+ discard SendMessageA(window.fHandle, WM_SETICON, cast[pointer](ICON_SMALL), icon)
+
+
+# ----------------------------------------------------------------------------------------
+# Control
+# ----------------------------------------------------------------------------------------
+
+method pUpdateScrollBar(control: ControlImpl)
+
+proc init(control: ControlImpl) =
+ if control.fHandle == nil:
+ var dwStyle: int32 = WS_CHILD
+ control.fHandle = pCreateWindowExWithUserdata(pCustomControlWindowClass, dwStyle, 0, pDefaultParentWindow, cast[pointer](control))
+ procCall control.Control.init()
+
+method destroy(control: ControlImpl) =
+ procCall control.Control.destroy()
+ if control.canvas != nil:
+ control.canvas.destroy()
+ pDestroyWindow(control.fHandle)
+
+method `visible=`(control: ControlImpl, visible: bool) =
+ procCall control.Control.`visible=`(visible)
+ if visible:
+ pShowWindow(control.fHandle, SW_SHOW)
+ else:
+ pShowWindow(control.fHandle, SW_HIDE)
+
+method setSize(control: ControlImpl, width, height: int) =
+ procCall control.Control.setSize(width, height)
+ pSetWindowPos(control.fHandle, -1, -1, width, height, SWP_NOMOVE)
+ pUpdateScrollBar(control)
+
+method setPosition(control: ControlImpl, x, y: int) =
+ procCall control.Control.setPosition(x, y)
+ pSetWindowPos(control.fHandle, x, y, -1, -1, SWP_NOSIZE)
+
+method pUpdateScrollBar(control: ControlImpl) =
+ if control.fScrollableWidth == -1 and control.fScrollableHeight == -1:
+ return
+ # echo "control.pUpdateScrollBar " & control.tag
+
+ # Calculation of scrollbar settings:
+
+ control.fXScrollEnabled = false
+ control.fYScrollEnabled = false
+
+ if control.scrollableWidth > control.width:
+ control.fXScrollEnabled = true
+ if control.scrollableHeight > control.height:
+ control.fYScrollEnabled = true
+
+ if control.fXScrollEnabled and not control.fYScrollEnabled and control.scrollableHeight > control.height - fScrollbarSize:
+ control.fYScrollEnabled = true
+ if control.fYScrollEnabled and not control.fXScrollEnabled and control.scrollableWidth > control.width - fScrollbarSize:
+ control.fXScrollEnabled = true
+
+ # Apply settings:
+
+ discard ShowScrollBar(control.fHandle, SB_HORZ, control.fXScrollEnabled)
+ if control.fXScrollEnabled:
+ var si: ScrollInfo
+ si.cbSize = ScrollInfo.sizeOf.int32
+ si.fMask = SIF_ALL
+ si.nMin = 0
+ si.nMax = control.fScrollableWidth.int32
+ if control.fYScrollEnabled:
+ si.nMax.inc(fScrollbarSize)
+ si.nPage = control.width.int32
+ si.nPos = control.fXScrollPos.int32
+ si.nTrackPos = 0
+ discard SetScrollInfo(control.fHandle, SB_HORZ, si, false)
+ # Ensure that scroll pos is within range:
+ control.fXScrollPos = max(min(control.fXScrollPos, si.nMax - control.width), 0)
+ else:
+ control.fXScrollPos = 0
+
+ discard ShowScrollBar(control.fHandle, SB_VERT, control.fYScrollEnabled)
+ if control.fYScrollEnabled:
+ var si: ScrollInfo
+ si.cbSize = ScrollInfo.sizeOf.int32
+ si.fMask = SIF_ALL
+ si.nMin = 0
+ si.nMax = control.fScrollableHeight.int32
+ if control.fXScrollEnabled:
+ si.nMax.inc(fScrollbarSize)
+ si.nPage = control.height.int32
+ si.nPos = control.fYScrollPos.int32
+ si.nTrackPos = 0
+ discard SetScrollInfo(control.fHandle, SB_VERT, si, false)
+ # Ensure that scroll pos is within range:
+ control.fYScrollPos = max(min(control.fYScrollPos, si.nMax - control.height), 0)
+ else:
+ control.fYScrollPos = 0
+
+method `xScrollPos=`(control: ControlImpl, xScrollPos: int) =
+ procCall control.Control.`xScrollPos=`(xScrollPos)
+ control.pUpdateScrollBar()
+ control.forceRedraw()
+
+method `yScrollPos=`(control: ControlImpl, yScrollPos: int) =
+ procCall control.Control.`yScrollPos=`(yScrollPos)
+ control.pUpdateScrollBar()
+ control.forceRedraw()
+
+method `scrollableWidth=`(control: ControlImpl, scrollableWidth: int) =
+ procCall control.Control.`scrollableWidth=`(scrollableWidth)
+ control.pUpdateScrollBar()
+
+method `scrollableHeight=`(control: ControlImpl, scrollableHeight: int) =
+ procCall control.Control.`scrollableHeight=`(scrollableHeight)
+ control.pUpdateScrollBar()
+
+method forceRedraw(control: ControlImpl) = discard InvalidateRect(control.fHandle, nil, true)
+
+proc pUpdateFont(control: ControlImpl) =
+ if control.fFont != nil:
+ discard DeleteObject(control.fFont)
+ control.fFont = CreateFontA(control.fontSize.int32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, control.fontFamily)
+ discard SendMessageA(control.fHandle, WM_SETFONT, control.fFont, cast[pointer](true))
+
+method setFontFamily(control: ControlImpl, fontFamily: string) =
+ procCall control.Control.setFontFamily(fontFamily)
+ control.pUpdateFont()
+
+method setFontSize(control: ControlImpl, fontSize: int) =
+ procCall control.Control.setFontSize(fontSize)
+ control.pUpdateFont()
+
+# method `setBackgroundColor=`(control: ControlImpl, color: Color) =
+ # procCall control.Control.setBackgroundColor(color)
+ # var brush = CreateSolidBrush(color.pColorToRGB32())
+ # discard SetClassLongPtrA(control.fHandle, GCLP_HBRBACKGROUND, brush)
+ # no effect
+
+proc pGetTextSize(control: ControlImpl, text: string): Size =
+ let hdc = GetDC(control.fHandle)
+ result = pGetTextSize(hdc, control.fFont, text)
+ discard DeleteDC(hdc)
+
+method focus(control: ControlImpl) =
+ discard SetFocus(control.fHandle)
+
+method getTextLineWidth(control: ControlImpl, text: string): int = control.pGetTextSize(text).cx
+
+method getTextLineHeight(control: ControlImpl): int = control.pGetTextSize("a").cy
+
+proc pCommonControlWndProc_Scroll(origWndProc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer =
+ const lineSize = 15
+ case wParam.loWord
+ of SB_THUMBPOSITION, SB_THUMBTRACK:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if control != nil:
+ if uMsg == WM_HSCROLL:
+ control.xScrollPos = wParam.hiWord
+ else:
+ control.yScrollPos = wParam.hiWord
+ of SB_LINELEFT:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if uMsg == WM_HSCROLL:
+ control.xScrollPos = control.xScrollPos - lineSize
+ else:
+ control.yScrollPos = control.yScrollPos - lineSize
+ of SB_PAGELEFT:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if uMsg == WM_HSCROLL:
+ control.xScrollPos = control.xScrollPos - control.width
+ else:
+ control.yScrollPos = control.yScrollPos - control.height
+ of SB_LINERIGHT:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if uMsg == WM_HSCROLL:
+ control.xScrollPos = control.xScrollPos + lineSize
+ else:
+ control.yScrollPos = control.yScrollPos + lineSize
+ of SB_PAGERIGHT:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if uMsg == WM_HSCROLL:
+ control.xScrollPos = control.xScrollPos + control.width
+ else:
+ control.yScrollPos = control.yScrollPos + control.height
+ else:
+ discard
+
+proc pCommonControlWndProc(origWndProc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer =
+ case uMsg
+ of WM_KEYDOWN:
+ let control = cast[Control](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if control != nil:
+ # echo "control WM_KEYDOWN"
+ pHandleWMKEYDOWN(control.parentWindow, control, wParam, lParam)
+
+ # of WM_KEYUP:
+ # return nil # key is still inserted in text area
+
+ of WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN:
+ let control = cast[Control](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if control != nil:
+ discard SetFocus(control.parentWindow.fHandle)
+ # TODO: request if is focusable
+ var button: MouseButton
+ var x = loWord(lParam)
+ var y = hiWord(lParam)
+ case uMsg
+ of WM_LBUTTONDOWN: button = MouseButton_Left
+ of WM_RBUTTONDOWN: button = MouseButton_Right
+ of WM_MBUTTONDOWN: button = MouseButton_Middle
+ else: discard
+ var event = new MouseButtonEvent
+ event.control = control
+ event.button = button
+ event.x = x
+ event.y = y
+ control.handleMouseButtonDownEvent(event)
+ pLastMouseButtonDownControl = control
+ pLastMouseButtonDownControlX = x
+ pLastMouseButtonDownControlY = y
+ of WM_LBUTTONUP, WM_RBUTTONUP, WM_MBUTTONUP:
+ let control = cast[Control](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if control != nil:
+ var button: MouseButton
+ var x = loWord(lParam)
+ var y = hiWord(lParam)
+ if x >= 0 and x < control.width and y >= 0 and y < control.height:
+ # send event only, when mouse was over control
+ case uMsg
+ of WM_LBUTTONUP: button = MouseButton_Left
+ of WM_RBUTTONUP: button = MouseButton_Right
+ of WM_MBUTTONUP: button = MouseButton_Middle
+ else: discard
+ var event = new MouseButtonEvent
+ event.control = control
+ event.button = button
+ event.x = x
+ event.y = y
+ control.handleMouseButtonUpEvent(event)
+ if uMsg == WM_LBUTTONUP and control == pLastMouseButtonDownControl and abs(x - pLastMouseButtonDownControlX) <= clickMaxXYMove and abs(y - pLastMouseButtonDownControlY) <= clickMaxXYMove:
+ var clickEvent = new ClickEvent
+ clickEvent.control = control
+ control.handleClickEvent(clickEvent)
+ of WM_HSCROLL, WM_VSCROLL:
+ result = pCommonControlWndProc_Scroll(origWndProc, hWnd, uMsg, wParam, lParam)
+ else:
+ discard
+ result = CallWindowProcW(origWndProc, hWnd, uMsg, wParam, lParam)
+
+proc pCustomControlWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer =
+ case uMsg
+ of WM_PAINT:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if control != nil:
+ var ps: PaintStruct
+ var dc = BeginPaint(hWnd, ps)
+ if dc == nil: pRaiseLastOSError()
+ var event = new DrawEvent
+ event.control = control
+ var canvas = cast[CanvasImpl](control.canvas)
+ if canvas == nil:
+ canvas = newCanvas(control)
+ else:
+ if canvas.fFont != nil:
+ discard SelectObject(dc, canvas.fFont)
+ canvas.fDC = dc
+ pCheckGdiplusStatus(GdipCreateFromHDC(dc, canvas.fGraphics))
+ discard SetBkMode(dc, TRANSPARENT)
+ control.handleDrawEvent(event)
+ discard EndPaint(hWnd, ps)
+ canvas.fDC = nil
+ canvas.fGraphics = nil
+ of WM_MOUSEWHEEL:
+ let scrolled = wParam.hiWord div 120
+ echo "wheel: " & $scrolled
+ # of WM_ERASEBKGND: # no effect
+ # return false
+ of WM_SETFOCUS:
+ # echo "control WM_SETFOCUS"
+ discard
+ else:
+ discard
+ result = pCommonControlWndProc(pCommonWndProc, hWnd, uMsg, wParam, lParam)
+
+
+# ----------------------------------------------------------------------------------------
+# Container
+# ----------------------------------------------------------------------------------------
+
+proc pContainerWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer =
+ case uMsg
+ of WM_ERASEBKGND:
+ let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ if control != nil:
+ var brush = CreateSolidBrush(control.backgroundColor.pColorToRGB32)
+ var rect = pGetClientRect(control.fHandle)
+ discard FillRect(wParam, rect, brush)
+ return
+ else:
+ discard
+ result = pCustomControlWndProc(hWnd, uMsg, wParam, lParam)
+
+proc init(container: ContainerImpl) =
+ var dwStyle: int32 = WS_CHILD
+ container.fHandle = pCreateWindowExWithUserdata(pContainerWindowClass, dwStyle, 0, pDefaultParentWindow, cast[pointer](container))
+ # ScrollWnd:
+ container.fScrollWndHandle = pCreateWindowExWithUserdata(pContainerWindowClass, dwStyle, 0, container.fHandle, cast[pointer](container))
+ pShowWindow(container.fScrollWndHandle, SW_SHOW)
+ # Inner:
+ container.fInnerHandle = pCreateWindowExWithUserdata(pContainerWindowClass, dwStyle, 0, container.fScrollWndHandle, cast[pointer](container))
+ pShowWindow(container.fInnerHandle, SW_SHOW)
+ container.Container.init()
+
+proc pUpdateScrollWnd(container: ContainerImpl) =
+ let padding = container.getPadding()
+ let width = container.width - padding.left - padding.right
+ let height = container.height - padding.top - padding.bottom
+ pSetWindowPos(container.fScrollWndHandle, padding.left, padding.top, width, height)
+
+method `frame=`(container: ContainerImpl, frame: Frame) =
+ procCall container.Container.`frame=`(frame)
+ if frame != nil:
+ pSetParent(frame.fHandle, container.fHandle)
+ container.pUpdateScrollWnd()
+
+method add(container: ContainerImpl, control: ControlImpl) =
+ procCall container.Container.add(control)
+ pSetParent(control.fHandle, container.fInnerHandle)
+
+method remove(container: ContainerImpl, control: ControlImpl) =
+ procCall container.Container.remove(control)
+ pSetParent(control.fHandle, pDefaultParentWindow)
+
+method setInnerSize(container: ContainerImpl, width, height: int) =
+ procCall container.Container.setInnerSize(width, height)
+ pSetWindowPos(container.fInnerHandle, -1, -1, width, height, SWP_NOMOVE)
+
+method setSize(container: ContainerImpl, width, height: int) =
+ procCall container.Container.setSize(width, height)
+ container.pUpdateScrollWnd()
+
+proc pSetInnerPos(container: ContainerImpl) =
+ pSetWindowPos(container.fInnerHandle, -container.xScrollPos, -container.yScrollPos, -1, -1, SWP_NOSIZE)
+
+method `xScrollPos=`(container: ContainerImpl, xScrollPos: int) =
+ procCall container.ControlImpl.`xScrollPos=`(xScrollPos)
+ container.pSetInnerPos()
+
+method `yScrollPos=`(container: ContainerImpl, yScrollPos: int) =
+ procCall container.ControlImpl.`yScrollPos=`(yScrollPos)
+ container.pSetInnerPos()
+
+
+# ----------------------------------------------------------------------------------------
+# Frame
+# ----------------------------------------------------------------------------------------
+
+proc init(frame: NativeFrame) =
+ const dwStyle = WS_CHILD or BS_GROUPBOX or WS_GROUP
+ frame.fHandle = pCreateWindowExWithUserdata("BUTTON", dwStyle, 0, pDefaultParentWindow, cast[pointer](frame))
+ frame.Frame.init()
+
+method `text=`(frame: NativeFrame, text: string) =
+ procCall frame.Frame.`text=`(text)
+ pSetWindowText(frame.fHandle, text)
+
+method naturalWidth(frame: NativeFrame): int = frame.getTextLineWidth(frame.text) + 10
+
+method getPadding(frame: NativeFrame): Spacing =
+ result = procCall frame.Frame.getPadding()
+ result.top = frame.getTextLineHeight() * frame.text.countLines + 2
+
+
+# ----------------------------------------------------------------------------------------
+# Button
+# ----------------------------------------------------------------------------------------
+
+var pButtonOrigWndProc: pointer
+
+proc pButtonWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} =
+ case uMsg
+ of WM_KEYDOWN:
+ let button = cast[Button](GetWindowLongPtrW(hWnd, GWLP_USERDATA))
+ # if button != nil and (cast[int](wParam) == 13 or cast[int](wParam) == 32):
+ if button != nil and cast[int](wParam) == 13:
+ var event = new ClickEvent
+ event.control = button
+ button.handleClickEvent(event)
+ else:
+ discard
+ result = pCommonControlWndProc(pButtonOrigWndProc, hWnd, uMsg, wParam, lParam)
+
+proc init(button: NativeButton) =
+ button.fHandle = pCreateWindowExWithUserdata("BUTTON", WS_CHILD or WS_TABSTOP, 0, pDefaultParentWindow, cast[pointer](button))
+ # WS_TABSTOP does not work, why?
+ pButtonOrigWndProc = SetWindowLongPtrW(button.fHandle, GWLP_WNDPROC, pButtonWndProc)
+ button.Button.init()
+
+method `text=`(button: NativeButton, text: string) =
+ procCall button.Button.`text=`(text)
+ pSetWindowText(button.fHandle, text)
+
+
+# ----------------------------------------------------------------------------------------
+# Label
+# ----------------------------------------------------------------------------------------
+
+proc init(label: NativeLabel) =
+ label.fHandle = pCreateWindowExWithUserdata("STATIC", WS_CHILD or SS_CENTERIMAGE, 0, pDefaultParentWindow, cast[pointer](label))
+ label.Label.init()
+
+method `text=`(label: NativeLabel, text: string) =
+ procCall label.Label.`text=`(text)
+ pSetWindowText(label.fHandle, text)
+
+
+# ----------------------------------------------------------------------------------------
+# TextBox
+# ----------------------------------------------------------------------------------------
+
+var pTextBoxOrigWndProc: pointer
+
+proc pTextBoxWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} =
+ result = pCommonControlWndProc(pTextBoxOrigWndProc, hWnd, uMsg, wParam, lParam)
+
+proc init(textBox: NativeTextBox) =
+ textBox.fHandle = pCreateWindowExWithUserdata("EDIT", WS_CHILD, WS_EX_CLIENTEDGE, pDefaultParentWindow, cast[pointer](textBox))
+ pTextBoxOrigWndProc = SetWindowLongPtrW(textBox.fHandle, GWLP_WNDPROC, pTextBoxWndProc)
+ textBox.TextBox.init()
+
+method text(textBox: NativeTextBox): string = pGetWindowText(textBox.fHandle)
+
+method `text=`(textBox: NativeTextBox, text: string) = pSetWindowText(textBox.fHandle, text)
+
+method naturalHeight(textBox: NativeTextBox): int = textBox.getTextLineHeight() + 9 # add padding
+
+
+# ----------------------------------------------------------------------------------------
+# TextArea
+# ----------------------------------------------------------------------------------------
+
+var pTextAreaOrigWndProc: pointer
+
+proc pTextAreaWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} =
+ # Handle Ctrl+A:
+ # TODO: Move this to handleKeyDownEvent(), so it's overridable by the control
+ if uMsg == WM_KEYDOWN and cast[char](wParam) == 'A' and GetKeyState(VK_CONTROL) <= -127:
+ discard SendMessageA(hwnd, EM_SETSEL, nil, cast[pointer](-1))
+ return nil
+ result = pCommonControlWndProc(pTextAreaOrigWndProc, hWnd, uMsg, wParam, lParam)
+
+proc init(textArea: NativeTextArea) =
+ var dwStyle: int32 = WS_CHILD or ES_MULTILINE or WS_VSCROLL # with wrap
+ # var dwStyle: int32 = WS_CHILD or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL # no wrap
+ var dwExStyle: int32 = WS_EX_CLIENTEDGE
+ textArea.fHandle = pCreateWindowExWithUserdata("EDIT", dwStyle, dwExStyle, pDefaultParentWindow, cast[pointer](textArea))
+ pTextAreaOrigWndProc = SetWindowLongPtrW(textArea.fHandle, GWLP_WNDPROC, pTextAreaWndProc)
+ textArea.TextArea.init()
+
+method text(textArea: NativeTextArea): string = pGetWindowText(textArea.fHandle)
+
+method `text=`(textArea: NativeTextArea, text: string) = pSetWindowText(textArea.fHandle, text)
+
+method scrollToBottom(textArea: NativeTextArea) =
+ # select all
+ discard SendMessageA(textArea.fHandle, EM_SETSEL, nil, cast[pointer](-1))
+ # unselect and stay at the end pos
+ discard SendMessageA(textArea.fHandle, EM_SETSEL, cast[pointer](-1), cast[pointer](-1))
+ # set scrollcaret to the current pos
+ discard SendMessageA(textArea.fHandle, EM_SCROLLCARET, nil, nil)
+
+method `wrap=`(textArea: NativeTextArea, wrap: bool) =
+ procCall textArea.TextArea.`wrap=`(wrap)
+ # TODO: allow to enable/disable word draw at runtime
+ # It seems that this is not possible.
+ # Word wrap depends on whether dwStyle contains WS_HSCROLL at window creation.
+ # Changing the style later has not the wanted effect.
diff --git a/src/windows/nigui_platform_types1.nim b/src/windows/nigui_platform_types1.nim
new file mode 100755
index 0000000..ddf36cf
--- /dev/null
+++ b/src/windows/nigui_platform_types1.nim
@@ -0,0 +1,23 @@
+# NiGui - Win32 platform-specific code - part 1
+
+# This file will be included in "nigui.nim".
+
+type
+ WindowImpl* = ref object of Window
+ fHandle: pointer
+ fModalParent: WindowImpl
+
+ ControlImpl* = ref object of Control
+ fHandle: pointer
+ fFont: pointer
+
+ CanvasImpl* = ref object of Canvas
+ fDC: pointer
+ fBitmap: pointer
+ fGraphics: pointer
+ fFont: pointer
+ fFontBrush: pointer
+ fLinePen: pointer
+ fAreaBrush: pointer
+
+ ImageImpl* = ref object of Image
diff --git a/src/windows/nigui_platform_types2.nim b/src/windows/nigui_platform_types2.nim
new file mode 100755
index 0000000..bbe7caf
--- /dev/null
+++ b/src/windows/nigui_platform_types2.nim
@@ -0,0 +1,18 @@
+# NiGui - Win32 platform-specific code - part 2
+
+# This file will be included in "nigui.nim".
+
+type
+ ContainerImpl* = ref object of Container
+ fScrollWndHandle: pointer
+ fInnerHandle: pointer
+
+ NativeFrame* = ref object of Frame
+
+ NativeButton* = ref object of Button
+
+ NativeLabel* = ref object of Label
+
+ NativeTextBox* = ref object of TextBox
+
+ NativeTextArea* = ref object of TextArea
diff --git a/src/windows/windows.nim b/src/windows/windows.nim
new file mode 100755
index 0000000..543985a
--- /dev/null
+++ b/src/windows/windows.nim
@@ -0,0 +1,452 @@
+# NiGui - minimal Win32 binding
+
+# Some functions requires Windows XP or newer.
+# Windows type names are replaced with basic types.
+
+# Type aliases for int16:
+# ATOM, SHORT, USHORT, LANGID
+
+# Type aliases for int32:
+# int, UINT, WINUINT, DWORD, LONG, COLORREF
+
+# Type aliases for int:
+# WPARAM, LPARAM, ULONG
+
+# Type aliases for pointer:
+# WNDPROC, HINSTANCE, HICON, HCURSOR, HBRUSH, HWND, LPMSG, LRESULT, PACTCTX, HMODULE, HDC, HGDIOBJ, HFONT, HMONITOR, HGDIOBJ
+
+# Type aliases for cstring:
+# LPCTSTR, LPCWSTR
+
+{.pragma: libUser32, stdcall, dynlib: "User32.dll".}
+{.pragma: libKernel32, stdcall, dynlib: "Kernel32.dll".}
+{.pragma: libGdi32, stdcall, dynlib: "Gdi32.dll".}
+{.pragma: libShell32, stdcall, dynlib: "Shell32.dll".}
+{.pragma: libGdiplus, stdcall, dynlib: "Gdiplus.dll".}
+{.pragma: libComdlg32, stdcall, dynlib: "Comdlg32.dll".}
+
+
+# ----------------------------------------------------------------------------------------
+# Constants
+# ----------------------------------------------------------------------------------------
+
+const
+ ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID* = 4
+ ACTCTX_FLAG_RESOURCE_NAME_VALID* = 8
+ ACTCTX_FLAG_SET_PROCESS_DEFAULT* = 16
+ BN_CLICKED* = 0
+ BM_SETIMAGE* = 247
+ BS_GROUPBOX* = 0x00000007
+ COLOR_BTNFACE* = 15
+ COLOR_WINDOWTEXT* = 8
+ CP_UTF8* = 65001
+ CS_HREDRAW* = 2
+ CS_VREDRAW* = 1
+ CW_USEDEFAULT* = 0x80000000.int
+ DEFAULT_GUI_FONT* = 17
+ EM_SCROLLCARET* = 183
+ EM_SETSEL* = 177
+ EN_CHANGE* = 768
+ ES_MULTILINE* = 4
+ GCLP_HBRBACKGROUND* = -10
+ GWL_EXSTYLE* = -20
+ GWL_HINSTANCE* = -6
+ GWL_HWNDPARENT* = -8
+ GWL_STYLE* = -16
+ GWLP_USERDATA* = -21
+ GWLP_WNDPROC* = -4
+ ICON_SMALL* = 0
+ ICON_BIG* = 1
+ IDC_ARROW* = 32512
+ INVALID_HANDLE_VALUE* = cast[pointer](-1)
+ IMAGE_BITMAP* = 0
+ IMAGE_ICON* = 1
+ # LR_LOADFROMFILE* = 16
+ OBJ_BRUSH* = 2
+ PM_REMOVE* = 1
+ SB_HORZ* = 0
+ SB_THUMBPOSITION* = 4
+ SB_THUMBTRACK* = 5
+ SB_VERT* = 1
+ SB_LINELEFT* = 0
+ SB_LINERIGHT* = 1
+ SB_PAGELEFT* = 2
+ SB_PAGERIGHT* = 3
+ SIF_ALL* = 23
+ SM_CXVSCROLL* = 2
+ SPI_GETWORKAREA* = 0x0030
+ SPI_SETKEYBOARDCUES* = 0x100B
+ SS_CENTERIMAGE* = 0x00000200
+ SW_HIDE* = 0
+ SW_SHOW* = 5
+ SWP_NOMOVE* = 2
+ SWP_NOSIZE* = 1
+ VK_CONTROL* = 17
+ VK_MENU* = 18
+ WM_ACTIVATE* = 0x0006
+ WM_CHANGEUISTATE* = 0x0127
+ WM_CHAR* = 258
+ WM_CLOSE* = 16
+ WM_COMMAND* = 273
+ WM_DROPFILES* = 563
+ WM_ERASEBKGND* = 20
+ WM_HSCROLL* = 276
+ WM_KEYDOWN* = 256
+ WM_KEYUP* = 257
+ WM_LBUTTONDOWN* = 0x0201
+ WM_LBUTTONUP* = 0x0202
+ WM_MBUTTONDOWN* = 0x0207
+ WM_MBUTTONUP* = 0x0208
+ # WM_NCLBUTTONDOWN* = 161
+ # WM_NCLBUTTONUP* = 162
+ WM_MOUSEWHEEL* = 0x020A
+ WM_MOVE* = 3
+ WM_NEXTDLGCTL* = 0x0028
+ WM_PAINT* = 15
+ WM_RBUTTONDOWN* = 0x0204
+ WM_RBUTTONUP* = 0x0205
+ WM_SETFOCUS* = 0x0007
+ WM_SETFONT* = 48
+ WM_SIZE* = 5
+ WM_VSCROLL* = 277
+ WM_SETICON* = 128
+ WM_SYSKEYDOWN* = 260
+ WM_SYSCOMMAND* = 274
+ WM_CTLCOLOREDIT* = 307
+ WS_CLIPCHILDREN* = 0x02000000
+ WS_CAPTION* = 0x00C00000
+ WS_CHILD* = 0x40000000
+ WS_EX_CLIENTEDGE* = 0x00000200
+ WS_GROUP* = 0x00020000
+ WS_HSCROLL* = 0x00100000
+ WS_OVERLAPPEDWINDOW* = 0x00CF0000
+ WS_SYSMENU* = 0x00080000
+ WS_TABSTOP* = 0x00010000
+ WS_THICKFRAME* = 0x00040000
+ WS_VSCROLL* = 0x00200000
+ # DT_CALCRECT* = 1024
+ # OBJ_FONT* = 6
+ # SM_XVIRTUALSCREEN* = 76
+ # WC_LISTVIEWW* = "SysListView32"
+ # WC_TABCONTROLW* = "SysTabControl32"
+ # WC_TREEVIEWW* = "SysTreeView32"
+ # WM_CTLCOLORSTATIC* = 312
+ # WS_EX_TOOLWINDOW* = 0x00000080
+ OPAQUE* = 2
+ TRANSPARENT* = 1
+ PS_SOLID* = 0
+ PS_DASH* = 1
+ PS_DOT* = 2
+ PS_DASHDOT* = 3
+ PS_DASHDOTDOT* = 4
+ PS_NULL* = 5
+ PS_USERSTYLE* = 7
+ PS_INSIDEFRAME* = 6
+ OFN_ALLOWMULTISELECT* = 0x00000200
+ OFN_EXPLORER* = 0x00080000
+ OFN_FILEMUSTEXIST* = 0x00001000
+ OFN_OVERWRITEPROMPT* = 0x00000002
+ # UnitWorld* = 0
+ # UnitDisplay* = 1
+ UnitPixel* = 2
+ # UnitPoint* = 3
+ # UnitInch* = 4
+ # UnitDocument* = 5
+ # UnitMillimeter* = 6
+
+# ----------------------------------------------------------------------------------------
+# Types
+# ----------------------------------------------------------------------------------------
+
+type
+ WndClassEx* = object
+ cbSize*: int32
+ style*: int32
+ lpfnWndProc*: pointer
+ cbClsExtra*: int32
+ cbWndExtra*: int32
+ hInstance*: pointer
+ hIcon*: pointer
+ hCursor*: pointer
+ hbrBackground*: pointer
+ lpszMenuName*: cstring
+ lpszClassName*: cstring
+ hIconSm*: pointer
+
+ Point* = object
+ x*: int32
+ y*: int32
+
+ Size* = object
+ cx*: int32
+ cy*: int32
+
+ Rect* = object
+ left*: int32
+ top*: int32
+ right*: int32
+ bottom*: int32
+
+ RectF* = object
+ x*: cfloat
+ y*: cfloat
+ width*: cfloat
+ height*: cfloat
+
+ RGB32* = object
+ red*: byte
+ green*: byte
+ blue*: byte
+ unused: byte
+
+ ARGB* = object
+ blue*: byte
+ green*: byte
+ red*: byte
+ alpha*: byte
+
+ Msg* = object
+ hwnd*: pointer
+ message*: int32
+ wParam*: int
+ lParam*: int
+ time*: int32
+ pt*: Point
+
+ ActCtx* = object
+ cbSize*: int32
+ dwFlags*: int32
+ lpSource*: cstring
+ wProcessorArchitecture*: int16
+ wLangId*: int16
+ lpAssemblyDirectory*: cstring
+ lpResourceName*: cstring
+ lpApplicationName*: cstring
+ hModule*: pointer
+
+ ScrollInfo* = object
+ cbSize*: int32
+ fMask*: int32
+ nMin*: int32
+ nMax*: int32
+ nPage*: int32
+ nPos*: int32
+ nTrackPos*: int32
+
+ MonitorInfo * = object
+ cbSize*: int32
+ rcMonitor*: Rect
+ rcWork*: Rect
+ dwFlags*: int32
+
+ PaintStruct* = array[68, byte]
+
+ KeyState* = array[256, byte]
+
+ GdiplusStartupInput* = object
+ GdiplusVersion*: int32
+ DebugEventCallback*: pointer
+ SuppressBackgroundThread*: bool
+ SuppressExternalCodecs*: bool
+
+ OpenFileName* = object
+ lStructSize*: int32
+ hwndOwner*: pointer
+ hInstance*: pointer
+ lpstrFilter*: cstring
+ lpstrCustomFilter*: cstring
+ nMaxCustFilter*: int32
+ nFilterIndex*: int32
+ lpstrFile*: cstring
+ nMaxFile*: int32
+ lpstrFileTitle*: cstring
+ nMaxFileTitle*: int32
+ lpstrInitialDir*: cstring
+ lpstrTitle*: cstring
+ Flags*: int32
+ nFileOffset*: int16
+ nFileExtension*: int16
+ lpstrDefExt*: cstring
+ lCustData*: pointer
+ lpfnHook*: pointer
+ lpTemplateName*: cstring
+ # pvReserved: pointer
+ # dwReserved: int32
+ # FlagsEx*: int32
+
+ GUID * = object
+ Data1*: int32
+ Data2*: int32
+ Data3*: int32
+ Data4*: int32
+
+
+# ----------------------------------------------------------------------------------------
+# Replacement for Windows Macros
+# ----------------------------------------------------------------------------------------
+
+import math
+
+proc loWord*(param: pointer): int = cast[int](param) and 0x0000FFFF
+
+proc hiWord*(param: pointer): int =
+ result = (cast[int](param) shr 16) and 0xFFFF
+ if result > 2^15:
+ result = result - 2^16
+
+
+# ----------------------------------------------------------------------------------------
+# Kernel32 Procs
+# ----------------------------------------------------------------------------------------
+
+proc LoadLibraryA*(lpFileName: cstring): pointer {.importc: "LoadLibraryA", libKernel32.}
+# proc GetModuleHandleA*(lpModuleName: cstring): pointer {.importc: "GetModuleHandleA", libKernel32.}
+proc GetLastError*(): int {.importc: "GetLastError", libKernel32.}
+proc CreateActCtxA*(pActCtx: pointer): pointer {.importc: "CreateActCtxA", libKernel32.}
+proc ActivateActCtx*(pActCtx, lpCookie: pointer): bool {.importc: "ActivateActCtx", libKernel32.}
+proc GetSystemDirectoryA*(lpBuffer: pointer, uSize: int32): int32 {.importc: "GetSystemDirectoryA", libKernel32.}
+proc MultiByteToWideChar*(CodePage, dwFlags: int32, lpMultiByteStr: cstring, cbMultiByte: int32, lpWideCharStr: cstring, cchWideChar: int32): int32 {.importc: "MultiByteToWideChar", libKernel32.}
+proc WideCharToMultiByte*(CodePage, dwFlags: int32, lpWideCharStr: cstring, cchWideChar: int32, lpMultiByteStr: cstring, cbMultiByte: int32, lpDefaultChar: cstring, lpUsedDefaultChar: pointer): int32 {.importc: "WideCharToMultiByte", libKernel32.}
+
+proc MessageBoxA*(hWnd: pointer, lpText, lpCaption: cstring, uType: int) {.importc: "MessageBoxA", libUser32.}
+proc MessageBoxW*(hWnd: pointer, lpText, lpCaption: cstring, uType: int) {.importc: "MessageBoxW", libUser32.}
+
+proc RegisterClassExA*(lpwcx: var WndClassEx): int16 {.importc: "RegisterClassExA", libUser32.}
+proc CreateWindowExA*(dwExStyle: int32, lpClassName, lpWindowName: cstring, dwStyle: int32, x, y, nWidth, nHeight: int, hWndParent, hMenu, hInstance, lpParam: pointer): pointer {.importc: "CreateWindowExA", libUser32.}
+proc DestroyWindow*(hWnd: pointer): bool {.importc: "DestroyWindow", libUser32.}
+proc ShowWindow*(hWnd: pointer, nCmdShow: int32): bool {.importc: "ShowWindow", libUser32.}
+proc EnableWindow*(hWnd: pointer, bEnable: bool): bool {.importc: "EnableWindow", libUser32.}
+proc DefWindowProcA*(hWnd: pointer, uMsg: int, wParam, lParam: pointer): pointer {.importc: "DefWindowProcA", libUser32.}
+proc GetMessageA*(lpMsg, hWnd: pointer, wMsgFilterMin, wMsgFilterMax: int32): bool {.importc: "GetMessageA", libUser32.}
+proc PeekMessageA*(lpMsg, hWnd: pointer, wMsgFilterMin, wMsgFilterMax, wRemoveMsg: int32): bool {.importc: "PeekMessageA", libUser32.}
+proc TranslateMessage*(lpMsg: pointer): bool {.importc: "TranslateMessage", libUser32.}
+proc DispatchMessageA*(lpMsg: pointer): pointer {.importc: "DispatchMessageA", libUser32.}
+proc SetParent*(hWndChild, hWndNewParent: pointer): pointer {.importc: "SetParent", libUser32.}
+proc SetWindowLongA*(hWnd: pointer, nIndex, dwNewLong: int32): int32 {.importc: "SetWindowLongA", libUser32.}
+proc GetWindowLongA*(hWnd: pointer, nIndex: int32): int32 {.importc: "GetWindowLongA", libUser32.}
+proc SetWindowLongPtrW*(hWnd: pointer, nIndex: int32, dwNewLong: pointer): pointer {.importc: "SetWindowLongPtrW", libUser32.}
+proc GetWindowLongPtrW*(hWnd: pointer, nIndex: int32): pointer {.importc: "GetWindowLongPtrW", libUser32.}
+proc SetWindowTextA*(hWnd: pointer, lpString: cstring): bool {.importc: "SetWindowTextA", libUser32.}
+proc SetWindowTextW*(hWnd: pointer, lpString: cstring): bool {.importc: "SetWindowTextW", libUser32.}
+# proc GetWindowTextA*(hWnd: pointer, lpString: cstring, nMaxCount: int32): int32 {.importc: "GetWindowTextA", libUser32.}
+proc GetWindowTextW*(hWnd: pointer, lpString: cstring, nMaxCount: int32): int32 {.importc: "GetWindowTextW", libUser32.}
+# proc GetWindowTextLengthA*(hWnd: pointer): int32 {.importc: "GetWindowTextLengthA", libUser32.}
+proc GetWindowTextLengthW*(hWnd: pointer): int32 {.importc: "GetWindowTextLengthW", libUser32.}
+proc UpdateWindow*(hWnd: pointer): bool {.importc: "UpdateWindow", libUser32.}
+proc SetWindowPos*(wnd, hWndInsertAfter: pointer, x, y, cx, cy: int32, uFlags: int): bool {.importc: "SetWindowPos", libUser32.}
+proc MoveWindow*(wnd: pointer, x, y, nWidth, nHeight: int32, bRepaint: bool): bool {.importc: "MoveWindow", libUser32.}
+proc SetFocus*(hWnd: pointer): pointer {.importc: "SetFocus", libUser32.}
+proc GetWindowRect*(wnd: pointer, lpRect: var Rect): bool {.importc: "GetWindowRect", libUser32.}
+proc GetClientRect*(wnd: pointer, lpRect: var Rect): bool {.importc: "GetClientRect", libUser32.}
+proc BeginPaint*(hWnd: pointer, lpPaint: var PaintStruct): pointer {.importc: "BeginPaint", libUser32.}
+proc EndPaint*(hWnd: pointer, lpPaint: var PaintStruct): bool {.importc: "EndPaint", libUser32.}
+proc SendMessageA*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "SendMessageA", libUser32.}
+# proc SendMessageW*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "SendMessageW", libUser32.}
+proc PostMessageA*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc: "PostMessageA", libUser32.}
+proc GetSysColor*(nIndex: int32): RGB32 {.importc: "GetSysColor", libUser32.}
+proc SetClassLongPtrA*(hWnd: pointer, nIndex: int32, dwNewLong: pointer): pointer {.importc: "SetClassLongPtrA", libUser32.}
+proc InvalidateRect*(hWnd: pointer, lpRect: ref Rect, bErase: bool): bool {.importc: "InvalidateRect", libUser32.}
+proc PostQuitMessage*(nExitCode: int32) {.importc: "PostQuitMessage", libUser32.}
+proc GetDesktopWindow*(): pointer {.importc: "GetDesktopWindow", libUser32.}
+proc SystemParametersInfoA*(uiAction, uiParam: int32, pvParam: pointer, fWinIni: int32): bool {.importc: "SystemParametersInfoA", libUser32.}
+proc ClientToScreen*(hWnd: pointer, lpPoint: var Point): bool {.importc: "ClientToScreen", libUser32.}
+proc AdjustWindowRect*(lpRect: var Rect, dwStyle: int32, bMenu: bool): bool {.importc: "AdjustWindowRect", libUser32.}
+proc LoadCursorA*(hInstance: pointer, lpCursorName: cstring): pointer {.importc: "LoadCursorA", libUser32.}
+proc SetScrollInfo*(hWnd: pointer, fnBar: int32, lpsi: var ScrollInfo, fRedraw: bool): int32 {.importc: "SetScrollInfo", libUser32.}
+proc GetMonitorInfoA*(hMonitor: pointer, lpmi: var MonitorInfo): bool {.importc: "GetMonitorInfoA", libUser32.}
+proc MonitorFromRect*(lprc: var Rect, dwFlags: int32): pointer {.importc: "MonitorFromRect", libUser32.}
+proc GetSystemMetrics*(nIndex: int32): int32 {.importc: "GetSystemMetrics", libUser32.}
+proc CallWindowProcW*(lpPrevWndFunc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.importc: "CallWindowProcW", libUser32.}
+proc IsDialogMessageW*(hDlg, lpMsg: pointer): bool {.importc: "IsDialogMessageW", libUser32.}
+proc GetNextDlgTabItem*(hDlg, hCtl: pointer, bPrevious: bool): pointer {.importc: "GetNextDlgTabItem", libUser32.}
+proc GetParent*(hWnd: pointer): pointer {.importc: "GetParent", libUser32.}
+proc GetDC*(hWnd: pointer): pointer {.importc: "GetDC", libUser32.}
+# proc DrawTextW*(hdc: pointer, lpchText: cstring, nCount: int32, lpRect: var Rect, uFormat: int32): int32 {.importc: "DrawTextW", libUser32.}
+proc GetKeyboardState*(lpKeyState: var KeyState): bool {.importc: "GetKeyboardState", libUser32.}
+proc ToUnicode*(wVirtKey, wScanCode: int32, lpKeyState: var KeyState, pwszBuff: cstring, cchBuff, wFlags: int32): int32 {.importc: "ToUnicode", libUser32.}
+proc ShowScrollBar*(hWnd: pointer, wBar: int32, bShow: bool): bool {.importc: "ShowScrollBar", libUser32.}
+proc LoadImageW*(hinst: pointer, lpszName: cstring, uType, cxDesired, cyDesired, fuLoad: int32): int32 {.importc: "LoadImageW", libUser32.}
+proc SetTimer*(hWnd, nIDEvent: pointer, uElapse: int32, lpTimerFunc: pointer): pointer {.importc: "SetTimer", libUser32.}
+proc KillTimer*(hWnd, nIDEvent: pointer): bool {.importc: "KillTimer", libUser32.}
+proc FillRect*(hDC: pointer, lprc: var Rect, hbr: pointer): int32 {.importc: "FillRect", libUser32.}
+proc FrameRect*(hDC: pointer, lprc: var Rect, hbr: pointer): int32 {.importc: "FrameRect", libUser32.}
+proc GetKeyState*(nVirtKey: int32): int16 {.importc: "GetKeyState", libUser32.}
+
+
+# ----------------------------------------------------------------------------------------
+# GDI Procs
+# ----------------------------------------------------------------------------------------
+
+proc DeleteDC*(hdc: pointer): bool {.importc: "DeleteDC", libGdi32.}
+proc DeleteObject*(hObject: pointer): bool {.importc: "DeleteObject", libGdi32.}
+proc GetCurrentObject*(hdc: pointer, uObjectType: int32): pointer {.importc: "GetCurrentObject", libGdi32.}
+proc SelectObject*(hdc, hgdiobj: pointer): pointer {.importc: "SelectObject", libGdi32.}
+proc TextOutW*(hdc: pointer, nXStart, nYStart: int32, lpString: cstring, cchString: int32): bool {.importc: "TextOutW", libGdi32.}
+proc CreateSolidBrush*(crColor: RGB32): pointer {.importc: "CreateSolidBrush", libGdi32.}
+proc CreatePen*(fnPenStyle, nWidth: int32, crColor: RGB32): pointer {.importc: "CreatePen", libGdi32.}
+# proc GetStockObject*(fnObject: int32): pointer {.importc: "GetStockObject", libGdi32.}
+proc CreateFontA*(nHeight, nWidth, nEscapement, nOrientation, fnWeight, fdwItalic, fdwUnderline, fdwStrikeOut, fdwCharSet, fdwOutputPrecision, fdwClipPrecision, fdwQuality, fdwPitchAndFamily: int32, lpszFace: cstring): pointer {.importc: "CreateFontA", libGdi32.}
+proc GetTextExtentPoint32W*(hdc: pointer, lpString: cstring, c: int32, lpSize: var Size): bool {.importc: "GetTextExtentPoint32W", libGdi32.}
+proc SetBkMode*(hdc: pointer, iBkMode: int32): int32 {.importc: "SetBkMode", libGdi32.}
+proc SetTextColor*(hdc: pointer, crColor: RGB32): RGB32 {.importc: "SetTextColor", libGdi32.}
+proc SetBkColor*(hdc: pointer, crColor: RGB32): RGB32 {.importc: "SetBkColor", libGdi32.}
+proc MoveToEx*(hdc: pointer, x, y: int32, lpPoint: pointer): bool {.importc: "MoveToEx", libGdi32.}
+proc LineTo*(hdc: pointer, nXEnd, nYEnd: int): bool {.importc: "LineTo", libGdi32.}
+proc CreateCompatibleDC*(hdc: pointer): pointer {.importc: "CreateCompatibleDC", libGdi32.}
+proc SetPixel*(hdc: pointer, x, y: int32, crColor: RGB32): int32 {.importc: "SetPixel", libGdi32.}
+# proc BitBlt*(hdcDest: pointer, nXDest, nYDest, nWidth, nHeight: int32, hdcSrc: pointer, nXSrc, nYSrc, dwRop: int32): bool {.importc: "BitBlt", libGdi32.}
+
+
+# ----------------------------------------------------------------------------------------
+# GDI+ Procs
+# ----------------------------------------------------------------------------------------
+
+proc GdiplusStartup*(token: var pointer, input: var GdiplusStartupInput, output: pointer): int32 {.importc: "GdiplusStartup", libGdiplus.}
+proc GdipCreateBitmapFromFile*(filename: cstring, bitmap: var pointer): int32 {.importc: "GdipCreateBitmapFromFile", libGdiplus.}
+# proc GdipLoadImageFromFile*(filename: cstring, image: var pointer): int32 {.importc: "GdipLoadImageFromFile", libGdiplus.}
+proc GdipGetHicon*(bitmap: pointer, hicon: var pointer): int32 {.importc: "GdipCreateHICONFromBitmap", libGdiplus.}
+proc GdipCreateFromHDC*(hdc: pointer, graphics: var pointer): int32 {.importc: "GdipCreateFromHDC", libGdiplus.}
+proc GdipDeleteGraphics*(graphics: pointer): int32 {.importc: "GdipDeleteGraphics", libGdiplus.}
+proc GdipDrawImageRectI*(graphics, image: pointer, x, y, width, height: int32): int32 {.importc: "GdipDrawImageRectI", libGdiplus.}
+proc GdipGetImageWidth*(image: pointer, width: var int32): int32 {.importc: "GdipGetImageWidth", libGdiplus.}
+proc GdipGetImageHeight*(image: pointer, height: var int32): int32 {.importc: "GdipGetImageHeight", libGdiplus.}
+# proc GdipGetImageDimension*(image: pointer, width, height: var float): int32 {.importc: "GdipGetImageDimension", libGdiplus.}
+proc GdipCreateBitmapFromGraphics*(width, height: int32, target: pointer, bitmap: var pointer): int32 {.importc: "GdipCreateBitmapFromGraphics", libGdiplus.}
+proc GdipBitmapSetPixel*(bitmap: pointer, x, y: int32, color: ARGB): int32 {.importc: "GdipBitmapSetPixel", libGdiplus.}
+proc GdipSaveImageToFile*(image: pointer, filename: cstring, clsidEncoder, encoderParams: pointer): int32 {.importc: "GdipSaveImageToFile", libGdiplus.}
+# proc GdipGetImageEncodersSize*(numEncoders, size: var int32): int32 {.importc: "GdipGetImageEncodersSize", libGdiplus.}
+# proc GdipGetImageEncoders*(numEncoders, size: int32, encoders: pointer): int32 {.importc: "GdipGetImageEncoders", libGdiplus.}
+proc GdipGetImageGraphicsContext*(image: pointer, graphics: var pointer): int32 {.importc: "GdipGetImageGraphicsContext", libGdiplus.} # does not exist
+proc GdipDisposeImage*(image: pointer): int32 {.importc: "GdipDisposeImage", libGdiplus.}
+proc GdipFillRectangleI*(graphics, brush: pointer, x, y, width, height: int32): int32 {.importc: "GdipFillRectangleI", libGdiplus.}
+proc GdipDrawRectangleI*(graphics, pen: pointer, x, y, width, height: int32): int32 {.importc: "GdipDrawRectangleI", libGdiplus.}
+proc GdipDrawLineI*(graphics, pen: pointer, x1, y1, x2, y2: int32): int32 {.importc: "GdipDrawLineI", libGdiplus.}
+proc GdipCreateSolidFill*(color: ARGB, brush: var pointer): int32 {.importc: "GdipCreateSolidFill", libGdiplus.}
+proc GdipDeleteBrush*(brush: pointer): int32 {.importc: "GdipDeleteBrush", libGdiplus.}
+proc GdipCreatePen1*(color: ARGB, width: float, unit: int32, pen: var pointer): int32 {.importc: "GdipCreatePen1", libGdiplus.}
+proc GdipDeletePen*(pen: pointer): int32 {.importc: "GdipDeletePen", libGdiplus.}
+proc GdipDrawString*(graphics: pointer, `string`: cstring, length: int32, font: pointer, layoutRect: var RectF, stringFormat, brush: pointer): int32 {.importc: "GdipDrawString", libGdiplus.}
+proc GdipMeasureString*(graphics: pointer, `string`: cstring, length: int32, font: pointer, layoutRect: var RectF, stringFormat: pointer, boundingBox: var RectF, codepointsFitted, linesFilled: pointer): int32 {.importc: "GdipMeasureString", libGdiplus.}
+proc GdipCreateFont*(fontFamily: pointer, emSize: cfloat, style, unit: int32, font: var pointer): int32 {.importc: "GdipCreateFont", libGdiplus.}
+proc GdipDeleteFont*(font: pointer): int32 {.importc: "GdipDeleteFont", libGdiplus.}
+proc GdipCreateFontFamilyFromName*(name: cstring, fontCollection: pointer, fontFamily: var pointer): int32 {.importc: "GdipCreateFontFamilyFromName", libGdiplus.}
+proc GdipDeleteFontFamily*(fontFamily: pointer): int32 {.importc: "GdipDeleteFontFamily", libGdiplus.}
+
+
+# ----------------------------------------------------------------------------------------
+# Shell32 Procs
+# ----------------------------------------------------------------------------------------
+
+proc DragAcceptFiles*(hWnd: pointer, fAccept: bool) {.importc: "DragAcceptFiles", libShell32.}
+proc DragQueryFileW*(hDrop: pointer, iFile: uint32, lpszFile: cstring, cch: int32): int32 {.importc: "DragQueryFileW", libShell32.}
+proc DragFinish*(hDrop: pointer) {.importc: "DragFinish", libShell32.}
+
+
+# ----------------------------------------------------------------------------------------
+# Comdlg32 Procs
+# ----------------------------------------------------------------------------------------
+
+proc CommDlgExtendedError*(): int32 {.importc: "CommDlgExtendedError", libComdlg32.}
+proc GetOpenFileNameW*(lpofn: var OpenFileName): bool {.importc: "GetOpenFileNameW", libComdlg32.}
+proc GetSaveFileNameW*(lpofn: var OpenFileName): bool {.importc: "GetSaveFileNameW", libComdlg32.}
+