├── .github └── workflows │ └── test.yml ├── examples ├── config.nims ├── example_01_basic_app.nim ├── example_01_basic_app.png ├── example_02_controls.nim ├── example_03_layout_container.nim ├── example_04_msgboxes.nim ├── example_05_handle_window_close.nim ├── example_06_keyboard_events.nim ├── example_08_timers.nim ├── example_09_font_size.nim ├── example_10_drawing.nim ├── example_11_image_processing_cli.nim ├── example_12_file_dialogs.nim ├── example_13_fixed_layout.nim ├── example_14_container_scrolling.nim ├── example_15_threading.nim ├── example_30_custom_button.nim ├── example_31_custom_widget.nim ├── example_90_widget_error.nim ├── example_91_unhandled_exception.nim └── nim.cfg ├── faq.md ├── license.md ├── nigui.nimble ├── readme.md ├── screenshots.png └── src ├── nigui.nim └── nigui ├── msgbox.nim └── private ├── gtk3 ├── gtk3.nim ├── platform_impl.nim ├── platform_types1.nim ├── platform_types2.nim └── platform_types3.nim └── windows ├── platform_impl.nim ├── platform_types1.nim ├── platform_types2.nim ├── platform_types3.nim ├── res ├── amd64.o ├── i386.o ├── manifest.xml └── resource.rc └── windows.nim /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | skip: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - run: echo "Skip job" 12 | 13 | before: 14 | runs-on: ubuntu-latest 15 | if: "! contains(github.event.head_commit.message, '[skip ci]')" 16 | steps: 17 | - run: echo "not contains '[skip ci]'" 18 | 19 | test: 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | os: 24 | - ubuntu-latest 25 | - macOS-latest 26 | - windows-latest 27 | nim_version: 28 | - '1.2.0' 29 | - 'stable' 30 | needs: before 31 | steps: 32 | - uses: actions/checkout@v2 33 | 34 | - name: Set cache-key 35 | id: vars 36 | run: | 37 | if [[ ${{ matrix.nim_version }} == stable ]]; then 38 | echo ::set-output name=cache-key::$(date +%Y-%m-%d) 39 | else 40 | echo ::set-output name=cache-key::${{ matrix.nim_version }} 41 | fi 42 | shell: bash 43 | 44 | - name: Print cache-key 45 | run: echo cache-key = ${{ steps.vars.outputs.cache-key }} 46 | 47 | - name: Cache choosenim 48 | id: cache-choosenim 49 | uses: actions/cache@v1 50 | with: 51 | path: ~/.choosenim 52 | key: ${{ runner.os }}-choosenim-${{ steps.vars.outputs.cache-key }} 53 | if: runner.os != 'Windows' 54 | - name: Cache nimble 55 | id: cache-nimble 56 | uses: actions/cache@v1 57 | with: 58 | path: ~/.nimble 59 | key: ${{ runner.os }}-nimble-${{ hashFiles('*.nimble') }} 60 | if: runner.os != 'Windows' 61 | - uses: jiro4989/setup-nim-action@v1 62 | with: 63 | nim-version: ${{ matrix.nim_version }} 64 | 65 | - name: Test examples 66 | working-directory: examples 67 | run: | 68 | for file in $(ls -v example_*nim); do 69 | echo "=== test: $file ===" 70 | nim c "$file" 71 | echo "" 72 | done 73 | shell: bash 74 | 75 | mingw: 76 | runs-on: ubuntu-latest 77 | needs: before 78 | steps: 79 | - name: Install MinGW 80 | run: sudo apt install mingw-w64 81 | 82 | - uses: actions/checkout@v2 83 | 84 | - name: Set cache-key 85 | id: vars 86 | run: echo ::set-output name=cache-key::$(date +%Y-%m-%d) 87 | 88 | - name: Print cache-key 89 | run: echo cache-key = ${{ steps.vars.outputs.cache-key }} 90 | 91 | - name: Cache choosenim 92 | id: cache-choosenim 93 | uses: actions/cache@v1 94 | with: 95 | path: ~/.choosenim 96 | key: ${{ runner.os }}-choosenim-${{ steps.vars.outputs.cache-key }} 97 | 98 | - name: Cache nimble 99 | id: cache-nimble 100 | uses: actions/cache@v1 101 | with: 102 | path: ~/.nimble 103 | key: ${{ runner.os }}-nimble-${{ hashFiles('*.nimble') }} 104 | 105 | - uses: jiro4989/setup-nim-action@v1 106 | 107 | - name: Dump versions 108 | run: | 109 | nim -v 110 | nimble -v 111 | - name: Test examples 112 | working-directory: examples 113 | run: | 114 | for cpu in amd64 i386 115 | do 116 | for file in $(ls -v example_*nim) 117 | do 118 | echo "=== test: $file @$cpu ===" 119 | nim c -d:mingw -d:release --cpu:$cpu "$file" 120 | echo "" 121 | done 122 | done 123 | -------------------------------------------------------------------------------- /examples/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /examples/example_01_basic_app.nim: -------------------------------------------------------------------------------- 1 | # This example shows the basic use of the NiGui toolkit. 2 | 3 | import nigui 4 | # First, import the library. 5 | 6 | app.init() 7 | # Initialization is mandatory. 8 | 9 | var window = newWindow("NiGui Example") 10 | # Create a window with a given title: 11 | # By default, a window is empty and not visible. 12 | # It is played at the center of the screen. 13 | # A window can contain only one control. 14 | # A container can contain multiple controls. 15 | 16 | window.width = 600.scaleToDpi 17 | window.height = 400.scaleToDpi 18 | # Set the size of the window 19 | 20 | # window.iconPath = "example_01_basic_app.png" 21 | # The window icon can be specified this way. 22 | # The default value is the name of the executable file without extension + ".png" 23 | 24 | var container = newLayoutContainer(Layout_Vertical) 25 | # Create a container for controls. 26 | # By default, a container is empty. 27 | # It's size will adapt to it's child controls. 28 | # A LayoutContainer will automatically align the child controls. 29 | # The layout is set to clhorizontal. 30 | 31 | window.add(container) 32 | # Add the container to the window. 33 | 34 | var button = newButton("Button 1") 35 | # Create a button with a given title. 36 | 37 | container.add(button) 38 | # Add the button to the container. 39 | 40 | var textArea = newTextArea() 41 | # Create a multiline text box. 42 | # By default, a text area is empty and editable. 43 | 44 | container.add(textArea) 45 | # Add the text area to the container. 46 | 47 | button.onClick = proc(event: ClickEvent) = 48 | # Set an event handler for the "onClick" event (here as anonymous proc). 49 | 50 | textArea.addLine("Button 1 clicked, message box opened.") 51 | window.alert("This is a simple message box.") 52 | textArea.addLine("Message box closed.") 53 | 54 | window.show() 55 | # Make the window visible on the screen. 56 | # Controls (containers, buttons, ..) are visible by default. 57 | 58 | app.run() 59 | # At last, run the main loop. 60 | # This processes incoming events until the application quits. 61 | # To quit the application, dispose all windows or call "app.quit()". 62 | -------------------------------------------------------------------------------- /examples/example_01_basic_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonkrauter/NiGui/f8167a82517973d7cd47797801f90b06360e3f32/examples/example_01_basic_app.png -------------------------------------------------------------------------------- /examples/example_02_controls.nim: -------------------------------------------------------------------------------- 1 | # This example shows several controls of NiGui. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | 9 | var container = newLayoutContainer(Layout_Vertical) 10 | window.add(container) 11 | 12 | # Add a Button control: 13 | var button = newButton("Button") 14 | container.add(button) 15 | 16 | # Add a Checkbox control: 17 | var checkbox = newCheckbox("Checkbox") 18 | container.add(checkbox) 19 | 20 | # Add a ComboBox control: 21 | var comboBox = newComboBox(@["Option 1", "Option 2"]) 22 | container.add(comboBox) 23 | 24 | # Add a Label control: 25 | var label = newLabel("Label") 26 | container.add(label) 27 | 28 | # Add a Progress Bar control: 29 | var progressBar = newProgressBar() 30 | progressBar.value = 0.5 31 | container.add(progressBar) 32 | 33 | # Add a TextBox control: 34 | var textBox = newTextBox("TextBox") 35 | container.add(textBox) 36 | 37 | # Add a TextArea control: 38 | var textArea = newTextArea("TextArea\pLine 2\p") 39 | container.add(textArea) 40 | 41 | # Add more text to the TextArea: 42 | for i in 3..15: 43 | textArea.addLine("Line " & $i) 44 | 45 | # Add click event to the button: 46 | button.onClick = proc(event: ClickEvent) = 47 | textArea.addLine("Button clicked") 48 | 49 | window.show() 50 | 51 | app.run() 52 | -------------------------------------------------------------------------------- /examples/example_03_layout_container.nim: -------------------------------------------------------------------------------- 1 | # This example shows some possibilities to align controls with a LayoutContainer. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | # Row 1: Auto-sized: 8 | var innerContainer1 = newLayoutContainer(Layout_Horizontal) 9 | innerContainer1.frame = newFrame("Row 1: Auto-sized") 10 | for i in 1..3: 11 | var control = newButton("Button " & $i) 12 | innerContainer1.add(control) 13 | 14 | # Row 2: Auto-sized, more padding: 15 | var innerContainer2 = newLayoutContainer(Layout_Horizontal) 16 | innerContainer2.padding = 10 17 | innerContainer2.frame = newFrame("Row 2: Auto-sized, more padding") 18 | for i in 1..3: 19 | var control = newButton("Button " & $i) 20 | innerContainer2.add(control) 21 | 22 | # Row 3: Auto-sized, more spacing: 23 | var innerContainer3 = newLayoutContainer(Layout_Horizontal) 24 | innerContainer3.spacing = 15 25 | innerContainer3.frame = newFrame("Row 3: Auto-sized, more spacing") 26 | for i in 1..3: 27 | var control = newButton("Button " & $i) 28 | innerContainer3.add(control) 29 | 30 | # Row 4: Controls with WidthMode_Fill: 31 | var innerContainer4 = newLayoutContainer(Layout_Horizontal) 32 | innerContainer4.frame = newFrame("Row 4: Controls with WidthMode_Fill") 33 | innerContainer4.width = 600 34 | for i in 1..3: 35 | var control = newButton("Button " & $i) 36 | control.widthMode = WidthMode_Fill 37 | innerContainer4.add(control) 38 | 39 | # Row 5: Controls with WidthMode_Expand: 40 | var innerContainer5 = newLayoutContainer(Layout_Horizontal) 41 | innerContainer5.frame = newFrame("Row 5: Controls with WidthMode_Expand") 42 | for i in 1..3: 43 | var control = newButton("Button " & $i) 44 | control.widthMode = WidthMode_Expand 45 | innerContainer5.add(control) 46 | 47 | # Row 6: Controls centered: 48 | var innerContainer6 = newLayoutContainer(Layout_Horizontal) 49 | innerContainer6.widthMode = WidthMode_Expand 50 | innerContainer6.height = 80 # problem 51 | innerContainer6.xAlign = XAlign_Center 52 | innerContainer6.yAlign = YAlign_Center 53 | innerContainer6.frame = newFrame("Row 6: Controls centered") 54 | for i in 1..3: 55 | var control = newButton("Button " & $i) 56 | innerContainer6.add(control) 57 | 58 | # Row 7: Container expanded, spread: 59 | var innerContainer7 = newLayoutContainer(Layout_Horizontal) 60 | # innerContainer.height = 80 61 | innerContainer7.widthMode = WidthMode_Expand 62 | innerContainer7.xAlign = XAlign_Spread 63 | innerContainer7.frame = newFrame("Row 7: Container expanded, spread") 64 | for i in 1..3: 65 | var control = newButton("Button " & $i) 66 | innerContainer7.add(control) 67 | 68 | # Row 8: Static size: 69 | var innerContainer8 = newLayoutContainer(Layout_Horizontal) 70 | innerContainer8.widthMode = WidthMode_Expand 71 | innerContainer8.xAlign = XAlign_Center 72 | innerContainer8.yAlign = YAlign_Center 73 | innerContainer8.frame = newFrame("Row 8: Static size") 74 | for i in 1..3: 75 | var control = newButton("Button " & $i) 76 | control.width = 90 * i 77 | control.height = 15 * i 78 | innerContainer8.add(control) 79 | 80 | var mainContainer = newLayoutContainer(Layout_Vertical) 81 | mainContainer.add(innerContainer1) 82 | mainContainer.add(innerContainer2) 83 | mainContainer.add(innerContainer3) 84 | mainContainer.add(innerContainer4) 85 | mainContainer.add(innerContainer5) 86 | mainContainer.add(innerContainer6) 87 | mainContainer.add(innerContainer7) 88 | mainContainer.add(innerContainer8) 89 | 90 | var window = newWindow() 91 | window.width = 800 92 | window.height = 600 93 | window.add(mainContainer) 94 | window.show() 95 | 96 | app.run() 97 | -------------------------------------------------------------------------------- /examples/example_04_msgboxes.nim: -------------------------------------------------------------------------------- 1 | # This example shows different methods to create message boxes. 2 | 3 | import nigui 4 | import nigui/msgbox 5 | 6 | app.init() 7 | 8 | var window = newWindow() 9 | var mainContainer = newLayoutContainer(Layout_Vertical) 10 | window.add(mainContainer) 11 | 12 | var buttons = newLayoutContainer(Layout_Horizontal) 13 | mainContainer.add(buttons) 14 | 15 | var textArea = newTextArea() 16 | mainContainer.add(textArea) 17 | 18 | var button1 = newButton("Example 1") 19 | buttons.add(button1) 20 | button1.onClick = proc(event: ClickEvent) = 21 | window.alert("Hello.\n\nThis message box is created with \"alert()\".") 22 | textArea.addLine("Message box closed") 23 | 24 | var button2 = newButton("Example 2") 25 | buttons.add(button2) 26 | button2.onClick = proc(event: ClickEvent) = 27 | let res = window.msgBox("Hello.\n\nThis message box is created with \"msgBox()\".") 28 | textArea.addLine("Message box closed, result = " & $res) 29 | 30 | var button3 = newButton("Example 3") 31 | buttons.add(button3) 32 | button3.onClick = proc(event: ClickEvent) = 33 | 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") 34 | textArea.addLine("Message box closed, result = " & $res) 35 | 36 | window.show() 37 | app.run() 38 | -------------------------------------------------------------------------------- /examples/example_05_handle_window_close.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to handle a window close click. 2 | 3 | import nigui 4 | import nigui/msgbox 5 | 6 | app.init() 7 | 8 | var window = newWindow() 9 | 10 | window.onCloseClick = proc(event: CloseClickEvent) = 11 | case window.msgBox("Do you want to quit?", "Quit?", "Quit", "Minimize", "Cancel") 12 | of 1: window.dispose() 13 | of 2: window.minimize() 14 | else: discard 15 | 16 | window.show() 17 | app.run() 18 | -------------------------------------------------------------------------------- /examples/example_06_keyboard_events.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to handle keyboard events. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | 9 | var container = newLayoutContainer(Layout_Vertical) 10 | window.add(container) 11 | 12 | var textBox = newTextBox() 13 | container.add(textBox) 14 | 15 | var label = newLabel() 16 | 17 | 18 | window.onKeyDown = proc(event: KeyboardEvent) = 19 | label.text = label.text & "Window KeyDown event: key: " & $event.key & ", unicode: " & $event.unicode & ", character: " & event.character & ", down keys: " & $downKeys() & "\n" 20 | 21 | # Ctrl + Q -> Quit application 22 | if Key_Q.isDown() and Key_ControlL.isDown(): 23 | app.quit() 24 | 25 | textBox.onKeyDown = proc(event: KeyboardEvent) = 26 | label.text = label.text & "TextBox KeyDown event: key: " & $event.key & ", unicode: " & $event.unicode & ", character: " & event.character & ", down keys: " & $downKeys() & "\n" 27 | 28 | # Accept only digits 29 | if event.character.len > 0 and event.character[0].ord >= 32 and (event.character.len != 1 or event.character[0] notin '0'..'9'): 30 | event.handled = true 31 | 32 | 33 | container.add(label) 34 | 35 | window.show() 36 | textBox.focus() 37 | 38 | app.run() 39 | -------------------------------------------------------------------------------- /examples/example_08_timers.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to use timers. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | 9 | var mainContainer = newLayoutContainer(Layout_Vertical) 10 | window.add(mainContainer) 11 | var buttonContainer = newLayoutContainer(Layout_Horizontal) 12 | mainContainer.add(buttonContainer) 13 | 14 | var textArea = newTextArea() 15 | mainContainer.add(textArea) 16 | 17 | var timer: Timer 18 | var counter = 1 19 | 20 | proc timerProc(event: TimerEvent) = 21 | textArea.addLine($counter) 22 | counter.inc() 23 | 24 | var button1 = newButton("startTimer()") 25 | buttonContainer.add(button1) 26 | button1.onClick = proc(event: ClickEvent) = 27 | timer = startTimer(500, timerProc) 28 | 29 | var button2 = newButton("startRepeatingTimer()") 30 | buttonContainer.add(button2) 31 | button2.onClick = proc(event: ClickEvent) = 32 | timer = startRepeatingTimer(500, timerProc) 33 | 34 | var button3 = newButton("stopTimer()") 35 | buttonContainer.add(button3) 36 | button3.onClick = proc(event: ClickEvent) = 37 | timer.stop() 38 | 39 | window.show() 40 | 41 | app.run() 42 | -------------------------------------------------------------------------------- /examples/example_09_font_size.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to change the font size of controls. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | window.width = 500 9 | window.height = 600 10 | var container = newLayoutContainer(Layout_Vertical) 11 | window.add(container) 12 | 13 | for i in 12..20: 14 | var innerContainer = newLayoutContainer(Layout_Horizontal) 15 | container.add(innerContainer) 16 | innerContainer.frame = newFrame("Font size: " & $i) 17 | var button = newButton("Button") 18 | button.fontSize = i.float 19 | innerContainer.add(button) 20 | var label = newLabel("Label") 21 | label.fontSize = i.float 22 | innerContainer.add(label) 23 | 24 | window.show() 25 | app.run() 26 | -------------------------------------------------------------------------------- /examples/example_10_drawing.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to draw the surface of a control. 2 | 3 | import nigui 4 | 5 | app.init() 6 | var window = newWindow() 7 | window.width = 500 8 | window.height = 500 9 | 10 | var control1 = newControl() 11 | window.add(control1) 12 | # Creates a drawable control 13 | 14 | control1.widthMode = WidthMode_Fill 15 | control1.heightMode = HeightMode_Fill 16 | # Let it fill out the whole window 17 | 18 | var image1 = newImage() 19 | image1.loadFromFile("example_01_basic_app.png") 20 | # Reads the file and holds the image as bitmap in memory 21 | 22 | var image2 = newImage() 23 | image2.resize(2, 2) 24 | # Creates a new bitmap 25 | 26 | image2.canvas.setPixel(0, 0, rgb(255, 0, 0)) 27 | image2.canvas.setPixel(0, 1, rgb(255, 0, 0)) 28 | image2.canvas.setPixel(1, 1, rgb(0, 255, 0)) 29 | image2.canvas.setPixel(1, 0, rgb(0, 0, 255)) 30 | # Modifies single pixels 31 | 32 | control1.onDraw = proc (event: DrawEvent) = 33 | let canvas = event.control.canvas 34 | # A shortcut 35 | 36 | canvas.areaColor = rgb(30, 30, 30) # dark grey 37 | canvas.fill() 38 | # Fill the whole area 39 | 40 | canvas.setPixel(0, 0, rgb(255, 0, 0)) 41 | # Modifies a single pixel 42 | 43 | canvas.areaColor = rgb(255, 0, 0) # red 44 | canvas.drawRectArea(10, 10, 30, 30) 45 | # Draws a filled rectangle 46 | 47 | canvas.lineColor = rgb(255, 0, 0) # red 48 | canvas.drawLine(60, 10, 110, 40) 49 | # Draws a line 50 | 51 | let text = "Hello World!" 52 | canvas.textColor = rgb(0, 255, 0) # lime 53 | canvas.fontSize = 20 54 | canvas.fontFamily = "Arial" 55 | canvas.drawText(text, 10, 70) 56 | # Outputs a text 57 | 58 | canvas.drawRectOutline(10, 70, canvas.getTextWidth(text), canvas.getTextLineHeight()) 59 | # Draws a rectangle outline 60 | 61 | canvas.drawImage(image1, 10, 120) 62 | # Draws an image in original size 63 | 64 | canvas.drawImage(image2, 120, 120, 50) 65 | # Draws an image stretched 66 | 67 | control1.onMouseButtonDown = proc (event: MouseEvent) = 68 | echo(event.button, " (", event.x, ", ", event.y, ")") 69 | # Shows where the mouse is clicked in control-relative coordinates 70 | 71 | window.show() 72 | app.run() 73 | -------------------------------------------------------------------------------- /examples/example_11_image_processing_cli.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to do image processing without GUI 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var image1 = newImage() 8 | image1.loadFromFile("example_01_basic_app.png") 9 | # Reads the file and holds the image as bitmap in memory 10 | 11 | var image2 = newImage() 12 | image2.resize(200, 200) 13 | 14 | let canvas = image2.canvas 15 | # A shortcut 16 | 17 | canvas.areaColor = rgb(30, 30, 30) # dark grey 18 | canvas.fill() 19 | # Fill the whole area 20 | 21 | canvas.setPixel(0, 0, rgb(255, 0, 0)) 22 | # Modifies a single pixel 23 | 24 | canvas.areaColor = rgb(255, 0, 0) # red 25 | canvas.drawRectArea(10, 10, 30, 30) 26 | # Draws a filled rectangle 27 | 28 | canvas.lineColor = rgb(255, 0, 0) # red 29 | canvas.drawLine(60, 10, 110, 40) 30 | # Draws a line 31 | 32 | let text = "Hello World!" 33 | canvas.textColor = rgb(0, 255, 0) # lime 34 | canvas.fontSize = 20 35 | canvas.fontFamily = "Arial" 36 | canvas.drawText(text, 10, 70) 37 | # Outputs a text 38 | 39 | canvas.drawRectOutline(10, 70, canvas.getTextWidth(text), canvas.getTextLineHeight()) 40 | # Draws a rectangle outline 41 | 42 | canvas.drawImage(image1, 10, 120) 43 | # Draws an image in original size 44 | 45 | image2.saveToPngFile("out.png") 46 | # Save the image as PNG file 47 | 48 | -------------------------------------------------------------------------------- /examples/example_12_file_dialogs.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to use the Open File and Save File As dialogs. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | var mainContainer = newLayoutContainer(Layout_Vertical) 9 | window.add(mainContainer) 10 | 11 | var buttons = newLayoutContainer(Layout_Horizontal) 12 | mainContainer.add(buttons) 13 | 14 | var textArea = newTextArea() 15 | mainContainer.add(textArea) 16 | 17 | var button1 = newButton("Open ...") 18 | buttons.add(button1) 19 | button1.onClick = proc(event: ClickEvent) = 20 | var dialog = newOpenFileDialog() 21 | dialog.title = "Test Open" 22 | dialog.multiple = true 23 | # dialog.directory = "" 24 | dialog.run() 25 | textArea.addLine($dialog.files.len & " files selected") 26 | if dialog.files.len > 0: 27 | for file in dialog.files: 28 | textArea.addLine(file) 29 | 30 | var button2 = newButton("Save as ...") 31 | buttons.add(button2) 32 | button2.onClick = proc(event: ClickEvent) = 33 | var dialog = SaveFileDialog() 34 | dialog.title = "Test Save" 35 | # dialog.directory = "" 36 | dialog.defaultName = "default.txt" 37 | dialog.run() 38 | if dialog.file == "": 39 | textArea.addLine("No file selected") 40 | else: 41 | textArea.addLine(dialog.file) 42 | 43 | var button3 = newButton("Select Directory ...") 44 | buttons.add(button3) 45 | button3.onClick = proc(event: ClickEvent) = 46 | var dialog = SelectDirectoryDialog() 47 | dialog.title = "Test Select Directory" 48 | # dialog.startDirectory = "" 49 | dialog.run() 50 | if dialog.selectedDirectory == "": 51 | textArea.addLine("No directory selected") 52 | else: 53 | textArea.addLine(dialog.selectedDirectory) 54 | 55 | window.show() 56 | app.run() 57 | -------------------------------------------------------------------------------- /examples/example_13_fixed_layout.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to have controls in a fixed position. 2 | # This means that NiGui's automatic layout capabilities are not used and the controls have to be placed manually. 3 | 4 | import nigui 5 | 6 | app.init() 7 | 8 | var window = newWindow() 9 | 10 | var container = newContainer() 11 | window.add(container) 12 | 13 | # Add a Button control: 14 | var button = newButton("Button at 0,0") 15 | container.add(button) 16 | button.x = 0 17 | button.y = 0 18 | button.width = 200 19 | button.height = 50 20 | 21 | # Add another button: 22 | button = newButton("Button at 300,0") 23 | container.add(button) 24 | button.x = 300 25 | button.y = 0 26 | button.width = 200 27 | button.height = 50 28 | 29 | # Add another button: 30 | button = newButton("Button at 0,100") 31 | container.add(button) 32 | button.x = 0 33 | button.y = 100 34 | button.width = 200 35 | button.height = 50 36 | 37 | window.show() 38 | 39 | app.run() 40 | -------------------------------------------------------------------------------- /examples/example_14_container_scrolling.nim: -------------------------------------------------------------------------------- 1 | # This example shows an inner container with a scrollbar. 2 | # Result: 3 | # topContainer will take as many space as needed for the 5 labels. 4 | # scrollContainer takes the remaining space. 5 | # There is only one scrollbar: 6 | # The vertical scrollbar in scrollContainer, because it's height is insufficient for the 25 labels. 7 | # When the window height is increased, the scrollbar disappears. 8 | 9 | import NiGui 10 | 11 | app.init() 12 | 13 | var window = newWindow() 14 | window.width = 800 15 | window.height = 500 16 | 17 | var mainContainer = newLayoutContainer(Layout_Vertical) 18 | window.add(mainContainer) 19 | 20 | var topContainer = newLayoutContainer(Layout_Vertical) 21 | mainContainer.add(topContainer) 22 | 23 | for i in 1..5: 24 | topContainer.add(newLabel("Label in topContainer #" & $i)) 25 | 26 | var scrollContainer = newLayoutContainer(Layout_Vertical) 27 | mainContainer.add(scrollContainer) 28 | scrollContainer.heightMode = HeightMode_Expand 29 | scrollContainer.widthMode = WidthMode_Expand 30 | 31 | for i in 1..25: 32 | scrollContainer.add(newLabel("Label in scrollContainer #" & $i)) 33 | 34 | window.show() 35 | app.run() 36 | -------------------------------------------------------------------------------- /examples/example_15_threading.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to change a control from another thread 2 | 3 | import nigui 4 | 5 | var 6 | window: Window 7 | container: LayoutContainer 8 | button: Button 9 | pbar: ProgressBar 10 | thread: Thread[void] 11 | 12 | app.init() 13 | 14 | window = newWindow("NiGui Example") 15 | 16 | container = newLayoutContainer(Layout_Vertical) 17 | container.padding = 10 18 | window.add(container) 19 | 20 | button = newButton("Start thread") 21 | container.add(button) 22 | 23 | proc update() = 24 | {.gcsafe.}: 25 | pbar.value = pbar.value + 0.01 26 | app.sleep(100) 27 | app.queueMain(update) 28 | 29 | proc start() = 30 | {.gcsafe.}: 31 | app.queueMain(update) 32 | 33 | button.onClick = proc(event: ClickEvent) = 34 | container.remove(button) 35 | 36 | pbar = newProgressBar() 37 | container.add(pbar) 38 | 39 | createThread(thread, start) 40 | 41 | window.show() 42 | app.run() 43 | -------------------------------------------------------------------------------- /examples/example_30_custom_button.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to make a custom button (subclassing). 2 | 3 | import nigui 4 | 5 | 6 | # Definition of a custom button 7 | type CustomButton* = ref object of Button 8 | 9 | # Custom widgets need to draw themselves 10 | method handleDrawEvent(control: CustomButton, event: DrawEvent) = 11 | let canvas = event.control.canvas 12 | canvas.areaColor = rgb(55, 55, 55) 13 | canvas.textColor = rgb(255, 255, 255) 14 | canvas.lineColor = rgb(255, 255, 255) 15 | canvas.drawRectArea(0, 0, control.width, control.height) 16 | canvas.drawTextCentered(control.text) 17 | canvas.drawRectOutline(0, 0, control.width, control.height) 18 | 19 | # Override nigui.newButton (optional) 20 | proc newButton*(text = ""): Button = 21 | result = new CustomButton 22 | result.init() 23 | result.text = text 24 | 25 | 26 | # Main program 27 | 28 | app.init() 29 | 30 | var window = newWindow() 31 | 32 | var container = newLayoutContainer(Layout_Vertical) 33 | window.add(container) 34 | 35 | # Since newButton is overriden, this code is exactly the same as in a program using the native button 36 | var button = newButton("Button") 37 | container.add(button) 38 | 39 | var textArea = newTextArea() 40 | container.add(textArea) 41 | 42 | button.onClick = proc(event: ClickEvent) = 43 | textArea.addLine("Button clicked") 44 | 45 | window.show() 46 | 47 | app.run() 48 | -------------------------------------------------------------------------------- /examples/example_31_custom_widget.nim: -------------------------------------------------------------------------------- 1 | # This example shows how to make a custom widget (subclassing). 2 | 3 | import nigui 4 | 5 | 6 | # Definition of a custom widget 7 | type MyWidget* = ref object of ControlImpl 8 | counter: int 9 | 10 | # Custom widgets need to draw themselves 11 | method handleDrawEvent(control: MyWidget, event: DrawEvent) = 12 | let canvas = event.control.canvas 13 | canvas.areaColor = rgb(55, 55, 55) 14 | canvas.textColor = rgb(255, 255, 255) 15 | canvas.lineColor = rgb(255, 255, 255) 16 | canvas.drawRectArea(0, 0, control.width, control.height) 17 | canvas.drawTextCentered($control.counter) 18 | canvas.drawRectOutline(0, 0, control.width, control.height) 19 | 20 | # Custom click handler 21 | method handleClickEvent(control: MyWidget, event: ClickEvent) = 22 | procCall control.ControlImpl.handleClickEvent(event) 23 | control.counter.inc 24 | control.forceRedraw 25 | 26 | # Constructor (optional) 27 | proc newMyWidget*(): MyWidget = 28 | result = new MyWidget 29 | result.init() 30 | result.width = 100.scaleToDpi 31 | result.height = 50.scaleToDpi 32 | 33 | 34 | # Main program 35 | 36 | app.init() 37 | 38 | var window = newWindow() 39 | 40 | var container = newLayoutContainer(Layout_Vertical) 41 | window.add(container) 42 | 43 | var myWidget = newMyWidget() 44 | container.add(myWidget) 45 | 46 | var textArea = newTextArea() 47 | container.add(textArea) 48 | 49 | myWidget.onClick = proc(event: ClickEvent) = 50 | textArea.addLine("myWidget clicked") 51 | 52 | window.show() 53 | 54 | app.run() 55 | -------------------------------------------------------------------------------- /examples/example_90_widget_error.nim: -------------------------------------------------------------------------------- 1 | # This example shows what happens when a nigui widget error occurs. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | 9 | window.add(newButton("Button 1")) # ok 10 | window.add(newButton("Button 2")) # not ok, gives error messages, but program continues 11 | 12 | window.show() 13 | app.run() 14 | -------------------------------------------------------------------------------- /examples/example_91_unhandled_exception.nim: -------------------------------------------------------------------------------- 1 | # This example shows what happens when an unhandled exception occurs in the main loop. 2 | 3 | import nigui 4 | 5 | app.init() 6 | 7 | var window = newWindow() 8 | 9 | var container = newLayoutContainer(Layout_Vertical) 10 | window.add(container) 11 | 12 | var button = newButton("Raise Exception") 13 | container.add(button) 14 | button.onClick = proc(event: ClickEvent) = 15 | raise newException(Exception, "Test Exception") 16 | 17 | window.show() 18 | app.run() 19 | -------------------------------------------------------------------------------- /examples/nim.cfg: -------------------------------------------------------------------------------- 1 | --threads:on 2 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- 1 | NiGui FAQ 2 | ========= 3 | 4 | ### Is there a GUI builder planned? 5 | 6 | No, NiGui is intended to define your user interface by manually written Nim source code.
7 | Using a GUI builder is maybe faster at the start of a project, but to modify an existing UI, it faster to change a few lines of code, instead of dragging around a bunch of widgets. 8 | 9 | ### How big is the generated executable? 10 | 11 | Sizes of "example_01_basic_app":
12 | Linux x64 binary: 608 kB
13 | Windows x64 binary: 705 kB 14 | 15 | ### Does NiGui support customization? 16 | 17 | NiGui allows you to use the native controls like buttons, labels, and text boxes. Typically the look of native controls can't be changed much. For example Windows does not support changing the background color of a button.
18 | As an alternative to native controls, NiGui allows you to create custom controls. Custom controls must draw their surface by it's own, therefore it can look like anything you want. As an example: To create a custom button, you can inherit from the type `Button` and override the method `handleDrawEvent()`.
19 | To make it possible to adjust the look of custom controls to the look of native controls, NiGui allows you to use the platform's default styles, for example `app.defaultBackgroundColor` and `app.defaultTextColor`. 20 | 21 | ### How does NiGui compare to other GUI toolkits? 22 | 23 | *Please add your knowlege here if you know more.* 24 | 25 | #### NiGui compared to Gtk 26 | 27 | Gtk is a popular cross-platform GUI toolkit written in C.
28 | Gtk uses native widgets partly.
29 | Unlike to NiGui, under Windows the user has to install Gtk or you ship the DLL files with your application.
30 | The Gtk 2 DLL files for Windows takes about 30 MB.
31 | Under the desktop Linux distributions, Gtk 2 and 3 are usually preinstalled.
32 | NiGui uses Gtk 3 as backend under Linux.
33 | 34 | #### NiGui compared to Qt 35 | 36 | Qt is a popular cross-platform GUI toolkit written in C++.
37 | It uses a preprocessor for the C++ code, therefore it cannot be used with other programming languages like C or Nim. 38 | 39 | #### NiGui compared to wxWidgets 40 | 41 | wxWidgets is a cross-platform GUI toolkit written in C++.
42 | Unlike NiGui, under Windows the user has to install wxWidgets, or you have to ship the DLL files with your application, or you can statically link wxWidgets into your application.
43 | The wxWidgets DLL files for Windows takes about 20 MB (about the same overhead applies to static linking).
44 | wxWidgets can be used in Nim (https://github.com/Araq/wxnim/). 45 | 46 | #### NiGui compared to IUP 47 | 48 | IUP is a cross-platform GUI toolkit written in C (http://webserver2.tecgraf.puc-rio.br/iup/).
49 | Like NiGui, IUP uses Gtk and Win32 as backends.
50 | IUP can be used in Nim (https://github.com/nim-lang/iup). 51 | Using a C library in Nim is better than most languages but not perfect. For example the user has to install the library according to the instructions from the wrapper. With a pure Nim project you only need to install the package. 52 | 53 | #### NiGui compared to libui 54 | 55 | libui is a cross-platform GUI toolkit written in C (https://github.com/andlabs/libui).
56 | Like NiGui, libui uses Gtk and Win32 as backends and uses native controls.
57 | Like NiGui, libui is in the early development phase (at least no documentation is written).
58 | libui can be used in Nim (https://github.com/nim-lang/ui). 59 | Using a C library in Nim is better than most languages but not perfect. For example the user has to install the library according to the instructions from the wrapper. With a pure Nim project you only need to install the package. 60 | 61 | #### NiGui compared to nimx 62 | 63 | nimx is a cross-platform GUI toolkit written in Nim (https://github.com/yglukhov/nimx).
64 | It uses OpenGL as backend and also works in a web browser.
65 | There are no native controls.
66 | Like NiGui, nimx is in the early development phase (at least no documentation is written). 67 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Simon Krauter 2 | 3 | [MIT License](https://opensource.org/licenses/MIT) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /nigui.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.2.8" 4 | author = "Simon Krauter" 5 | description = "Cross-platform, desktop GUI toolkit using native widgets." 6 | license = "MIT" 7 | 8 | # Deps 9 | requires "nim >= 1.0.0" 10 | 11 | srcDir = "src" 12 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | NiGui 2 | ===== 3 | 4 | NiGui is a cross-platform desktop GUI toolkit written in [Nim](https://nim-lang.org/).
5 | NiGui provides an easy way to develop applications in Nim with a full-featured graphical user interface. 6 | 7 | Target platforms: 8 | * Windows (Win32 API) 9 | * Linux over GTK+ 3 10 | * macOS over GTK+ 3 (native support planned) 11 | 12 | Design goals: 13 | * **Full abstraction**
14 | 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. 15 | * **Simple and elegant**
16 | NiGui has a clean and beginner-friendly high-level API. It is much less complex than the Win32 API, GTK+ or Qt.
17 | NiGui profits of Nim's features and elegance in contrast to C code, for example Nim's polymorphism capabilities. 18 | * **Powerful**
19 | 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.
20 | NiGui has it's own layout manager for automatic resizing and positioning of controls. 21 | * **Minimal dependencies**
22 | The NiGui source code has no dependencies except Nim's standard library. Platform bindings are included.
23 | Generated binaries (exe files) include NiGui and do not need external libraries. 24 | 25 | Screenshots 26 | ------------- 27 | 28 | Example program with native controls running under Windows 10 and Xubuntu: 29 | 30 | 31 | Current state 32 | ------------- 33 | NiGui is currently work in progress. Very basic things work, many things are missing. 34 | 35 | Working: 36 | * Window, Button, Label, TextBox, TextArea 37 | * LayoutContainer (own layout manager) 38 | * Timers 39 | * Message boxes and file dialogs 40 | * Custom controls including scrolling 41 | * Drawing and image processing 42 | 43 | WIP: 44 | * Event handling 45 | * Documentation 46 | 47 | Planned: 48 | * macOS support 49 | * More widgets 50 | 51 | Getting started 52 | --------------- 53 | 54 | ### How to install NiGui manually 55 | 56 | 1. Clone the NiGui repository with Git or download the source code 57 | 2. Add the following line to one of your [Nim configuration files](https://nim-lang.org/docs/nimc.html#compiler-usage-configuration-files):
58 | `--path:"/src"` 59 | 60 | ### How to install NiGui with Nimble 61 | 62 | Run the [Nimble](https://github.com/nim-lang/nimble) install command: `$ nimble install nigui` 63 | 64 | ### Additional configuration 65 | 66 | * To disable the command line window under Windows, add this line to your Nim configuration: `--app:gui` 67 | * To compile a Windows binary which uses Gtk, add this line to your Nim configuration: `-d:forceGtk` 68 | 69 | ### How to verify the installation 70 | 71 | Compile and run one of the included example programs. 72 | 73 | Show cases 74 | ---------- 75 | * [NiCalc](https://github.com/trustable-code/NiCalc) - Simple calculator 76 | * [Gravitate](https://www.qtrac.eu/gravitate.html) - Samegame/tilefall-like game 77 | 78 | Contributing 79 | ------------ 80 | You can help to improve NiGui by: 81 | * Trying to use it and giving feedback 82 | * Test the programs under different Windows versions or Linux distributions 83 | * Developing show cases 84 | * Help improving and extending the code 85 | * Adding macOS support 86 | 87 | Contact: see https://github.com/trustable-code 88 | 89 | FAQ 90 | --- 91 | 92 | [FAQ](faq.md) 93 | 94 | License 95 | ------- 96 | NiGui is FLOSS (free and open-source software).
97 | All files in this repository are 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.
98 | Copyright 2017-2020 Simon Krauter and contributors 99 | -------------------------------------------------------------------------------- /screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonkrauter/NiGui/f8167a82517973d7cd47797801f90b06360e3f32/screenshots.png -------------------------------------------------------------------------------- /src/nigui/msgbox.nim: -------------------------------------------------------------------------------- 1 | # NiGui - extended message box 2 | 3 | # This module provides an extended message box. 4 | # The message box is shown as modal window. 5 | # The message box can have up to 3 buttons with customizable titles. 6 | # button1 will be focused. 7 | # Call the proc msgBox() to open the message box. 8 | # It will wait until the message box is closed. 9 | # Meaning of the result value: 10 | # 0 - message box was closed over the window close button or escape key 11 | # 1..3 - button 1..3 clicked 12 | 13 | # For an example see "example_04_msgboxes.nim". 14 | 15 | import nigui 16 | 17 | type MessageBoxWindow = ref object of WindowImpl 18 | clickedButton: Button 19 | 20 | proc buttonClick(event: ClickEvent) = 21 | cast[MessageBoxWindow](event.control.parentWindow).clickedButton = cast[Button](event.control) 22 | event.control.parentWindow.dispose() 23 | 24 | proc msgBox*(parent: Window, message: string, title = "Message", button1 = "OK", button2, button3: string = ""): int {.discardable.} = 25 | let buttonMinWidth = 100.scaleToDpi 26 | var window = new MessageBoxWindow 27 | window.init() 28 | window.title = title 29 | 30 | window.onKeyDown = proc(event: KeyboardEvent) = 31 | if event.key == Key_Escape: 32 | window.dispose() 33 | 34 | var container = newLayoutContainer(Layout_Vertical) 35 | container.padding = 10.scaleToDpi 36 | window.control = container 37 | 38 | var labelContainer = newLayoutContainer(Layout_Horizontal) 39 | container.add(labelContainer) 40 | labelContainer.widthMode = WidthMode_Expand 41 | labelContainer.heightMode = HeightMode_Expand 42 | 43 | var label = newLabel(message) 44 | labelContainer.add(label) 45 | 46 | var buttonContainer = newLayoutContainer(Layout_Horizontal) 47 | buttonContainer.widthMode = WidthMode_Expand 48 | buttonContainer.xAlign = XAlign_Center 49 | buttonContainer.spacing = 12.scaleToDpi 50 | container.add(buttonContainer) 51 | var b1, b2, b3: Button 52 | 53 | b1 = newButton(button1) 54 | b1.minWidth = buttonMinWidth 55 | b1.onClick = buttonClick 56 | buttonContainer.add(b1) 57 | b1.focus() 58 | 59 | if button2 != "": 60 | b2 = newButton(button2) 61 | b2.minWidth = buttonMinWidth 62 | b2.onClick = buttonClick 63 | buttonContainer.add(b2) 64 | 65 | if button3 != "": 66 | b3 = newButton(button3) 67 | b3.minWidth = buttonMinWidth 68 | b3.onClick = buttonClick 69 | buttonContainer.add(b3) 70 | 71 | window.width = min(max(label.width + 40.scaleToDpi, buttonMinWidth * 3 + 65.scaleToDpi), 600.scaleToDpi) 72 | window.height = min(label.height, 300.scaleToDpi) + buttonContainer.height + 70.scaleToDpi 73 | 74 | # Center message box on window: 75 | window.x = parent.x + ((parent.width - window.width) div 2) 76 | window.y = parent.y + ((parent.height - window.height) div 2) 77 | 78 | window.showModal(parent) 79 | 80 | while not window.disposed: 81 | app.sleep(100) 82 | 83 | if window.clickedButton == b1: 84 | result = 1 85 | elif window.clickedButton == b2 and b2 != nil: 86 | result = 2 87 | elif window.clickedButton == b3 and b3 != nil: 88 | result = 3 89 | -------------------------------------------------------------------------------- /src/nigui/private/gtk3/gtk3.nim: -------------------------------------------------------------------------------- 1 | # NiGui - minimal GTK+ 3 binding 2 | 3 | when defined(windows): 4 | const libgtk3Path* = "libgtk-3-0.dll" 5 | elif defined(macosx): 6 | const libgtk3Path* = "libgtk-3.0.dylib" 7 | else: 8 | const libgtk3Path* = "libgtk-3.so(|.0)" 9 | 10 | {.pragma: libgtk3, cdecl, dynlib: libgtk3Path.} 11 | 12 | # ---------------------------------------------------------------------------------------- 13 | # Types 14 | # ---------------------------------------------------------------------------------------- 15 | 16 | type 17 | Gboolean* = distinct cint 18 | 19 | GError* {.bycopy.} = object 20 | domain*: int32 21 | code*: cint 22 | message*: cstring 23 | 24 | GdkRectangle* {.bycopy.} = object 25 | x*, y*: cint 26 | width*, height*: cint 27 | 28 | GtkBorder* {.bycopy.} = object 29 | left*: int16 30 | right*: int16 31 | top*: int16 32 | bottom*: int16 33 | 34 | GdkRGBA* {.bycopy.} = object 35 | red*: cdouble 36 | green*: cdouble 37 | blue*: cdouble 38 | alpha*: cdouble 39 | 40 | GList* = ptr object 41 | data*: pointer 42 | next*: GList 43 | prev*: GList 44 | 45 | GtkTargetEntry* {.bycopy.} = object 46 | target*: cstring 47 | flags*: cint 48 | info*: cint 49 | 50 | GdkEventButton* {.bycopy.} = object 51 | event_type*: cint 52 | window*: pointer 53 | send_event*: int8 54 | time*: cint 55 | x*, y*: cdouble 56 | axes*: ptr cdouble 57 | state*: cint 58 | button*: cint 59 | device*: pointer 60 | x_root*, y_root*: cdouble 61 | 62 | GdkEventMotion* {.bycopy.} = object 63 | event_type*: cint 64 | window*: pointer 65 | send_event*: int8 66 | time*: cint 67 | x*, y*: cdouble 68 | axes*: ptr cdouble 69 | state*: cint 70 | is_hint*: int16 71 | device*: pointer 72 | x_root*, y_root*: cdouble 73 | 74 | GdkEventCrossing* {.bycopy.} = object 75 | event_type*: cint 76 | window*: pointer 77 | send_event*: int8 78 | subwindow*: pointer 79 | time*: cint 80 | x*, y*: cdouble 81 | x_root*, y_root*: cdouble 82 | mode*: cint 83 | detail*: cint 84 | focus*: bool 85 | device*: pointer 86 | state*: cint 87 | 88 | GdkEventKey* {.bycopy.} = object 89 | event_type*: cint 90 | window*: pointer 91 | send_event*: int8 92 | time*: cint 93 | state*: cint 94 | keyval*: cint 95 | length*: cint 96 | `string`*: cstring 97 | hardware_keycode*: int16 98 | group*: int8 99 | is_modifier*: int8 100 | 101 | GdkEventWindowState* {.bycopy.} = object 102 | event_type*: cint 103 | window*: pointer 104 | send_event*: int8 105 | changed_mask*: cint 106 | new_window_state*: cint 107 | 108 | GdkEventFocus* {.bycopy.} = object 109 | event_type*: cint 110 | window*: pointer 111 | send_event*: int8 112 | `in`*: int16 113 | 114 | GtkTextIter* {.bycopy.} = object 115 | dummy1: pointer 116 | dummy2: pointer 117 | dummy3: cint 118 | dummy4: cint 119 | dummy5: cint 120 | dummy6: cint 121 | dummy7: cint 122 | dummy8: cint 123 | dummy9: pointer 124 | dummy10: pointer 125 | dummy11: cint 126 | dummy12: cint 127 | dummy13: cint 128 | dummy14: pointer 129 | 130 | GdkGeometry* {.bycopy.} = object 131 | min_width*: cint 132 | min_height*: cint 133 | max_width*: cint 134 | max_height*: cint 135 | base_width*: cint 136 | base_height*: cint 137 | width_inc*: cint 138 | height_inc*: cint 139 | min_aspect*: cdouble 140 | max_aspect*: cdouble 141 | win_gravity*: cint 142 | 143 | 144 | converter gbool*(val: bool): Gboolean = ord(val).Gboolean 145 | 146 | converter toBool*(val: Gboolean): bool = int(val) != 0 147 | 148 | 149 | # ---------------------------------------------------------------------------------------- 150 | # Constants 151 | # ---------------------------------------------------------------------------------------- 152 | 153 | const 154 | # GtkWindowType: 155 | GTK_WINDOW_TOPLEVEL* = 0 156 | GTK_WINDOW_POPUP* = 1 157 | 158 | # GtkDestDefaults: 159 | # [..] 160 | GTK_DEST_DEFAULT_ALL* = 7 161 | 162 | # GdkDragAction: 163 | GDK_ACTION_DEFAULT* = 1 164 | GDK_ACTION_COPY* = 2 165 | GDK_ACTION_MOVE* = 4 166 | GDK_ACTION_LINK* = 8 167 | GDK_ACTION_PRIVATE* = 16 168 | GDK_ACTION_ASK* = 32 169 | 170 | # GtkOrientation: 171 | GTK_ORIENTATION_HORIZONTAL* = 0 172 | GTK_ORIENTATION_VERTICAL* = 1 173 | 174 | # GtkWrapMode: 175 | GTK_WRAP_NONE* = 0 176 | GTK_WRAP_CHAR* = 1 177 | GTK_WRAP_WORD* = 2 178 | GTK_WRAP_WORD_CHAR* = 3 179 | 180 | # GtkPolicyType: 181 | GTK_POLICY_ALWAYS* = 0 182 | GTK_POLICY_AUTOMATIC* = 1 183 | GTK_POLICY_NEVER* = 2 184 | GTK_POLICY_EXTERNAL* = 3 185 | 186 | # PangoEllipsizeMode: 187 | PANGO_ELLIPSIZE_NONE* = 0 188 | PANGO_ELLIPSIZE_START* = 1 189 | PANGO_ELLIPSIZE_MIDDLE* = 2 190 | PANGO_ELLIPSIZE_END* = 3 191 | 192 | # GtkButtonBoxStyle: 193 | GTK_BUTTONBOX_SPREAD* = 0 194 | GTK_BUTTONBOX_EDGE* = 1 195 | GTK_BUTTONBOX_START* = 2 196 | GTK_BUTTONBOX_END* = 3 197 | GTK_BUTTONBOX_CENTER* = 4 198 | GTK_BUTTONBOX_EXPAND* = 5 199 | 200 | # GtkJustification: 201 | GTK_JUSTIFY_LEFT* = 0 202 | GTK_JUSTIFY_RIGHT* = 1 203 | GTK_JUSTIFY_CENTER* = 2 204 | GTK_JUSTIFY_FILL* = 3 205 | 206 | # GtkStateFlags: 207 | GTK_STATE_FLAG_NORMAL* = 0 208 | # [..] 209 | 210 | # GdkEventMask: 211 | GDK_POINTER_MOTION_MASK* = 1 shl 2 212 | GDK_BUTTON_PRESS_MASK* = 1 shl 8 213 | GDK_BUTTON_RELEASE_MASK* = 1 shl 9 214 | GDK_KEY_PRESS_MASK* = 1 shl 10 215 | GDK_ENTER_NOTIFY_MASK* = 1 shl 12 216 | GDK_LEAVE_NOTIFY_MASK* = 1 shl 13 217 | # [..] 218 | 219 | # cairo_format_t: 220 | CAIRO_FORMAT_ARGB32* = 0 221 | # [..] 222 | 223 | # cairo_filter_t: 224 | CAIRO_FILTER_NEAREST* = 3 225 | CAIRO_FILTER_BILINEAR* = 4 226 | 227 | # GtkFileChooserAction: 228 | GTK_FILE_CHOOSER_ACTION_OPEN* = 0 229 | GTK_FILE_CHOOSER_ACTION_SAVE* = 1 230 | GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER* = 2 231 | GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER* = 3 232 | 233 | # GtkResponseType: 234 | GTK_RESPONSE_NONE* = -1 235 | GTK_RESPONSE_REJECT* = -2 236 | GTK_RESPONSE_ACCEPT* = -3 237 | GTK_RESPONSE_DELETE_EVENT* = -4 238 | GTK_RESPONSE_OK* = -5 239 | GTK_RESPONSE_CANCEL* = -6 240 | GTK_RESPONSE_CLOSE* = -7 241 | GTK_RESPONSE_YES* = -8 242 | GTK_RESPONSE_NO* = -9 243 | GTK_RESPONSE_APPLY* = -10 244 | GTK_RESPONSE_HELP* = -11 245 | 246 | # Selection: 247 | GDK_SELECTION_CLIPBOARD* = cast[pointer](69) 248 | 249 | # Key modifier masks: 250 | GDK_CONTROL_MASK* = 1 shl 2 251 | 252 | # GdkWindowState: 253 | GDK_WINDOW_STATE_WITHDRAWN* = 1 254 | GDK_WINDOW_STATE_ICONIFIED* = 2 255 | GDK_WINDOW_STATE_MAXIMIZED* = 4 256 | GDK_WINDOW_STATE_STICKY* = 8 257 | GDK_WINDOW_STATE_FULLSCREEN* = 16 258 | GDK_WINDOW_STATE_ABOVE* = 32 259 | GDK_WINDOW_STATE_BELOW* = 64 260 | GDK_WINDOW_STATE_FOCUSED* = 128 261 | GDK_WINDOW_STATE_TILED* = 256 262 | 263 | # GSignalMatchType: 264 | G_SIGNAL_MATCH_ID* = 1 265 | G_SIGNAL_MATCH_DETAIL* = 2 266 | G_SIGNAL_MATCH_CLOSURE* = 4 267 | G_SIGNAL_MATCH_FUNC* = 8 268 | G_SIGNAL_MATCH_DATA* = 16 269 | G_SIGNAL_MATCH_UNBLOCKED* = 16 270 | 271 | # GdkWindowTypeHint 272 | GDK_WINDOW_TYPE_HINT_NORMAL* = 0 273 | GDK_WINDOW_TYPE_HINT_DIALOG* = 1 274 | GDK_WINDOW_TYPE_HINT_MENU* = 2 275 | GDK_WINDOW_TYPE_HINT_TOOLBAR* = 3 276 | GDK_WINDOW_TYPE_HINT_SPLASHSCREEN* = 4 277 | GDK_WINDOW_TYPE_HINT_UTILITY* = 5 278 | GDK_WINDOW_TYPE_HINT_DOCK* = 6 279 | GDK_WINDOW_TYPE_HINT_DESKTOP* = 7 280 | GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU* = 8 281 | GDK_WINDOW_TYPE_HINT_POPUP_MENU* = 9 282 | GDK_WINDOW_TYPE_HINT_TOOLTIP* = 10 283 | GDK_WINDOW_TYPE_HINT_NOTIFICATION* = 11 284 | GDK_WINDOW_TYPE_HINT_COMBO* = 12 285 | GDK_WINDOW_TYPE_HINT_DND* = 13 286 | 287 | # GdkWindowHints 288 | GDK_HINT_MIN_SIZE* = 2 289 | 290 | 291 | # ---------------------------------------------------------------------------------------- 292 | # General Gtk Procs 293 | # ---------------------------------------------------------------------------------------- 294 | 295 | proc g_slist_length*(list: pointer): int {.importc, libgtk3.} 296 | proc g_slist_nth_data*(list: pointer, n: int): cstring {.importc, libgtk3.} 297 | proc g_object_ref*(`object`: pointer): pointer {.importc, libgtk3.} 298 | proc g_object_unref*(`object`: pointer) {.importc, libgtk3.} 299 | 300 | proc gtk_init*(argc, argv: pointer) {.importc, libgtk3.} 301 | 302 | proc gtk_main*() {.importc, libgtk3.} 303 | proc gtk_main_quit*() {.importc, libgtk3.} 304 | proc gtk_events_pending*(): cint {.importc, libgtk3.} 305 | proc gtk_main_iteration*(): cint {.importc, libgtk3.} 306 | proc g_timeout_add*(interval: cint, function, data: pointer): cint {.importc, libgtk3.} 307 | proc g_source_remove*(tag: cint): bool {.importc, libgtk3.} 308 | proc g_signal_connect_data*(instance: pointer, detailed_signal: cstring, c_handler: pointer, data, destroy_data, connect_flags: pointer = nil): pointer {.importc, libgtk3.} 309 | proc g_signal_handlers_block_matched*(instance: pointer, mask, signal_id: cint, detail, closure, function, data: pointer = nil) {.importc, libgtk3.} 310 | proc g_signal_handlers_unblock_matched*(instance: pointer, mask, signal_id: cint, detail, closure, function, data: pointer = nil) {.importc, libgtk3.} 311 | 312 | proc gtk_window_new*(`type`: cint): pointer {.importc, libgtk3.} 313 | proc gtk_window_set_title*(window: pointer, title: cstring) {.importc, libgtk3.} 314 | # proc gtk_window_get_title*(window: pointer): cstring {.importc, libgtk3.} 315 | proc gtk_window_set_transient_for*(window, parent: pointer) {.importc, libgtk3.} 316 | proc gtk_window_set_modal*(window: pointer, modal: cint) {.importc, libgtk3.} 317 | proc gtk_window_set_type_hint*(window: pointer, hint: cint) {.importc, libgtk3.} 318 | proc gtk_window_set_default_size*(window: pointer, width, height: cint) {.importc, libgtk3.} 319 | proc gtk_window_resize*(window: pointer, width, height: cint) {.importc, libgtk3.} 320 | proc gtk_window_resize_to_geometry*(window: pointer, width, height: cint) {.importc, libgtk3.} 321 | proc gtk_window_set_resizable*(window: pointer, resizable: bool) {.importc, libgtk3.} 322 | proc gtk_window_set_geometry_hints*(window: pointer, geometry_widget: pointer, geometry: var GdkGeometry, geom_mask: cint) {.importc, libgtk3.} 323 | proc gtk_window_get_size*(window: pointer, width, height: var cint) {.importc, libgtk3.} 324 | proc gtk_window_get_position*(window: pointer, x, y: var cint) {.importc, libgtk3.} 325 | proc gtk_window_move*(window: pointer, x, y: cint) {.importc, libgtk3.} 326 | proc gtk_window_set_icon_from_file*(window: pointer, filename: cstring, err: pointer): bool {.importc, libgtk3.} 327 | proc gtk_window_iconify*(window: pointer) {.importc, libgtk3.} 328 | proc gtk_window_deiconify*(window: pointer) {.importc, libgtk3.} 329 | proc gtk_window_present*(window: pointer) {.importc, libgtk3.} 330 | proc gtk_window_set_keep_above*(window: pointer, setting: bool) {.importc, libgtk3.} 331 | 332 | proc gdk_window_begin_paint_rect*(window: pointer, rectangle: var GdkRectangle) {.importc, libgtk3.} 333 | proc gdk_window_begin_paint_region*(window: pointer, region: pointer) {.importc, libgtk3.} 334 | proc gdk_window_end_paint*(window: pointer) {.importc, libgtk3.} 335 | proc gdk_window_get_clip_region*(window: pointer): pointer {.importc, libgtk3.} 336 | 337 | proc gtk_widget_destroy*(widget: pointer) {.importc, libgtk3.} 338 | proc gtk_widget_show*(widget: pointer) {.importc, libgtk3.} 339 | proc gtk_widget_hide*(widget: pointer) {.importc, libgtk3.} 340 | proc gtk_widget_set_size_request*(widget: pointer, width, height: cint) {.importc, libgtk3.} 341 | proc gtk_widget_size_allocate*(widget: pointer, allocation: var GdkRectangle) {.importc, libgtk3.} 342 | proc gtk_widget_get_size_request*(widget: pointer, width, height: var cint) {.importc, libgtk3.} 343 | proc gtk_widget_get_allocation*(widget: pointer, allocation: var GdkRectangle) {.importc, libgtk3.} 344 | # proc gtk_widget_set_allocation*(widget: pointer, allocation: var GdkRectangle) {.importc, libgtk3.} 345 | # proc gtk_widget_set_hexpand*(widget: pointer, expand: cint) {.importc, libgtk3.} 346 | proc gtk_widget_queue_draw*(widget: pointer) {.importc, libgtk3.} 347 | proc gtk_widget_set_margin_top*(widget: pointer, margin: cint) {.importc, libgtk3.} 348 | proc gtk_widget_add_events*(widget: pointer, events: cint) {.importc, libgtk3.} 349 | proc gtk_widget_set_can_focus*(widget: pointer, can_focus: cint) {.importc, libgtk3.} 350 | proc gtk_widget_modify_font*(widget: pointer, font_desc: pointer) {.importc, libgtk3.} 351 | proc gtk_widget_override_color*(widget: pointer, state: cint, color: var GdkRGBA) {.importc, libgtk3.} 352 | proc gtk_widget_override_background_color*(widget: pointer, state: cint, color: var GdkRGBA) {.importc, libgtk3.} 353 | proc gtk_widget_get_path*(widget: pointer): pointer {.importc, libgtk3.} 354 | proc gtk_widget_style_get*(widget: pointer, first_property_name: cstring, value: pointer, passNil: pointer) {.importc, libgtk3.} 355 | proc gtk_widget_get_style_context*(widget: pointer): pointer {.importc, libgtk3.} 356 | proc gtk_widget_grab_focus*(widget: pointer) {.importc, libgtk3.} 357 | proc gtk_widget_is_focus*(widget: pointer): bool {.importc, libgtk3.} 358 | proc gtk_widget_realize*(widget: pointer) {.importc, libgtk3.} 359 | proc gtk_widget_draw*(widget, cr: pointer) {.importc, libgtk3.} 360 | proc gtk_widget_set_sensitive*(widget: pointer, sensitive: bool) {.importc, libgtk3.} 361 | proc gtk_widget_get_pointer*(widget: pointer, x, y: var cint) {.importc, libgtk3.} 362 | 363 | proc gtk_container_add*(container, widget: pointer) {.importc, libgtk3.} 364 | proc gtk_container_remove*(container, widget: pointer) {.importc, libgtk3.} 365 | # proc gtk_container_foreach*(container, callback, callback_data: pointer) {.importc, libgtk3.} 366 | proc gtk_container_get_children*(container: pointer): GList {.importc, libgtk3.} 367 | proc gtk_container_set_border_width*(container: pointer, width: cint) {.importc, libgtk3.} 368 | 369 | proc gtk_fixed_new*(): pointer {.importc, libgtk3.} 370 | proc gtk_fixed_move*(fixed, widget: pointer, x, y: cint) {.importc, libgtk3.} 371 | 372 | proc gtk_layout_new*(hadjustment, vadjustment: pointer): pointer {.importc, libgtk3.} 373 | # proc gtk_layout_put*(layout, child_widget: pointer, x, y: cint) {.importc, libgtk3.} 374 | proc gtk_layout_move*(layout, child_widget: pointer, x, y: cint) {.importc, libgtk3.} 375 | proc gtk_layout_set_size*(layout: pointer, width, height: cint) {.importc, libgtk3.} 376 | # proc gtk_layout_get_hadjustment*(layout: pointer): pointer {.importc, libgtk3.} 377 | # proc gtk_layout_get_vadjustment*(layout: pointer): pointer {.importc, libgtk3.} 378 | 379 | # proc gtk_scrollbar_new*(orientation: GtkOrientation, adjustment: pointer): pointer {.importc, libgtk3.} 380 | 381 | proc gtk_dialog_new*(): pointer {.importc, libgtk3.} 382 | proc gtk_dialog_run*(dialog: pointer): cint {.importc, libgtk3.} 383 | proc gtk_dialog_add_button*(dialog: pointer, button_text: cstring, response_id: cint): pointer {.importc, libgtk3.} 384 | proc gtk_dialog_get_content_area*(dialog: pointer): pointer {.importc, libgtk3.} 385 | proc gtk_dialog_get_action_area*(dialog: pointer): pointer {.importc, libgtk3.} 386 | 387 | proc gtk_file_chooser_dialog_new*(title: cstring, parent: pointer, action: int, text1: cstring, response1: int, text2: cstring, response2: int, ending: pointer): pointer {.importc, libgtk3.} 388 | proc gtk_file_chooser_set_current_name*(chooser: pointer, name: cstring): bool {.importc, libgtk3.} 389 | proc gtk_file_chooser_get_filename*(chooser: pointer): cstring {.importc, libgtk3.} 390 | proc gtk_file_chooser_get_filenames*(chooser: pointer): pointer {.importc, libgtk3.} 391 | proc gtk_file_chooser_set_select_multiple*(chooser: pointer, select_multiple: bool) {.importc, libgtk3.} 392 | proc gtk_file_chooser_set_current_folder*(chooser: pointer, filename: cstring): bool {.importc, libgtk3.} 393 | 394 | proc gtk_button_box_set_layout*(widget: pointer, layout_style: cint) {.importc, libgtk3.} 395 | 396 | # proc gtk_message_dialog_new*(parent: pointer, flags: GtkDialogFlags, `type`: GtkMessageType, buttons: GtkButtonsType, message_format: cstring): pointer {.importc, libgtk3.} 397 | 398 | proc gtk_label_new*(str: cstring): pointer {.importc, libgtk3.} 399 | # proc gtk_label_set_text*(label: pointer, str: cstring) {.importc, libgtk3.} 400 | # proc gtk_label_get_text*(label: pointer): cstring {.importc, libgtk3.} 401 | proc gtk_label_set_ellipsize*(label: pointer, mode: cint) {.importc, libgtk3.} 402 | # proc gtk_label_set_justify*(label: pointer, jtype: cint) {.importc, libgtk3.} 403 | # proc gtk_label_set_xalign*(label: pointer, xalign: cfloat) {.importc, libgtk3.} 404 | # proc gtk_label_set_yalign*(label: pointer, yalign: cfloat) {.importc, libgtk3.} 405 | 406 | proc gtk_progress_bar_new*(): pointer {.importc, libgtk3.} 407 | proc gtk_progress_bar_set_fraction*(pbar: pointer, fraction: cdouble) {.importc, libgtk3.} 408 | 409 | proc gtk_level_bar_new*(): pointer {.importc, libgtk3.} 410 | proc gtk_level_bar_set_value*(pbar: pointer, value: cdouble) {.importc, libgtk3.} 411 | 412 | # proc gtk_box_new*(orientation: GtkOrientation, spacing: cint): pointer {.importc, libgtk3.} 413 | proc gtk_box_pack_start*(box, child: pointer, expand, fill: cint, padding: cint) {.importc, libgtk3.} 414 | 415 | proc gtk_button_new*(): pointer {.importc, libgtk3.} 416 | # proc gtk_button_new_with_label*(label: cstring): pointer {.importc, libgtk3.} 417 | # proc gtk_button_get_label*(button: pointer): cstring {.importc, libgtk3.} 418 | proc gtk_button_set_label*(button: pointer, label: cstring) {.importc, libgtk3.} 419 | 420 | proc gtk_check_button_new*(): pointer {.importc, libgtk3.} 421 | proc gtk_toggle_button_set_active*(toggle_button: pointer, is_active: bool) {.importc, libgtk3.} 422 | proc gtk_toggle_button_get_active*(toggle_button: pointer): bool {.importc, libgtk3.} 423 | 424 | proc gtk_combo_box_text_new*(): pointer {.importc, libgtk3.} 425 | proc gtk_combo_box_get_active*(combo_box: pointer): cint {.importc, libgtk3.} 426 | proc gtk_combo_box_set_active*(combo_box: pointer, index: cint) {.importc, libgtk3.} 427 | proc gtk_combo_box_text_get_active_text*(combo_box: pointer): cstring {.importc, libgtk3.} 428 | proc gtk_combo_box_text_append_text*(combo_box: pointer, text: cstring) {.importc, libgtk3.} 429 | proc gtk_combo_box_text_remove_all*(combo_box: pointer) {.importc, libgtk3.} 430 | 431 | proc gtk_entry_new*(): pointer {.importc, libgtk3.} 432 | proc gtk_entry_set_text*(entry: pointer, text: cstring) {.importc, libgtk3.} 433 | proc gtk_entry_get_text*(entry: pointer): cstring {.importc, libgtk3.} 434 | proc gtk_entry_set_width_chars*(entry: pointer, n_chars: cint) {.importc, libgtk3.} 435 | proc gtk_entry_set_placeholder_text*(entry: pointer, text: cstring) {.importc, libgtk3.} 436 | proc gtk_editable_get_selection_bounds*(editable: pointer, start_pos, end_pos: var cint): bool {.importc, libgtk3.} 437 | proc gtk_editable_get_chars*(editable: pointer, start_pos, end_pos: cint): cstring {.importc, libgtk3.} 438 | proc gtk_editable_select_region*(editable: pointer, start_pos, end_pos: cint) {.importc, libgtk3.} 439 | proc gtk_editable_get_position*(editable: pointer): cint {.importc, libgtk3.} 440 | proc gtk_editable_set_position*(editable: pointer, position: cint) {.importc, libgtk3.} 441 | proc gtk_editable_set_editable*(editable: pointer, is_editable: bool) {.importc, libgtk3.} 442 | 443 | proc gtk_text_view_new*(): pointer {.importc, libgtk3.} 444 | proc gtk_text_view_set_buffer*(text_view, buffer: pointer) {.importc, libgtk3.} 445 | proc gtk_text_view_get_buffer*(text_view: pointer): pointer {.importc, libgtk3.} 446 | proc gtk_text_view_set_wrap_mode*(text_view: pointer, wrap_mode: cint) {.importc, libgtk3.} 447 | proc gtk_text_view_set_left_margin*(text_view: pointer, margin: cint) {.importc, libgtk3.} 448 | proc gtk_text_view_set_right_margin*(text_view: pointer, margin: cint) {.importc, libgtk3.} 449 | proc gtk_text_view_set_top_margin*(text_view: pointer, margin: cint) {.importc, libgtk3.} 450 | proc gtk_text_view_set_bottom_margin*(text_view: pointer, margin: cint) {.importc, libgtk3.} 451 | proc gtk_text_view_scroll_to_iter*(text_view: pointer, iter: var GtkTextIter, within_margin: cdouble, use_align: bool, xalign, yalign: cdouble) {.importc, libgtk3.} 452 | # proc gtk_text_view_scroll_to_mark*(text_view, mark: pointer, within_margin: cdouble, use_align: bool, xalign, yalign: cdouble) {.importc, libgtk3.} 453 | # proc gtk_text_view_place_cursor_onscreen*(text_view: pointer): bool {.importc, libgtk3.} 454 | proc gtk_text_view_set_editable*(text_view: pointer, setting: bool) {.importc, libgtk3.} 455 | 456 | # proc gtk_text_buffer_new*(table: pointer): pointer {.importc, libgtk3.} 457 | proc gtk_text_buffer_set_text*(text_buffer: pointer, text: cstring, len: cint) {.importc, libgtk3.} 458 | proc gtk_text_buffer_get_text*(text_buffer: pointer, start, `end`: var GtkTextIter, include_hidden_chars: bool): cstring {.importc, libgtk3.} 459 | proc gtk_text_buffer_get_start_iter*(text_buffer: pointer, iter: var GtkTextIter) {.importc, libgtk3.} 460 | proc gtk_text_buffer_get_end_iter*(text_buffer: pointer, iter: var GtkTextIter) {.importc, libgtk3.} 461 | # proc gtk_text_buffer_add_mark*(buffer, mark: pointer, where: var GtkTextIter) {.importc, libgtk3.} 462 | proc gtk_text_buffer_get_insert*(buffer: pointer): pointer {.importc, libgtk3.} 463 | # proc gtk_text_buffer_get_iter_at_line*(buffer: pointer, iter: var GtkTextIter, line_number: cint) {.importc, libgtk3.} 464 | proc gtk_text_buffer_insert*(buffer: pointer, iter: var GtkTextIter, text: cstring, len: cint) {.importc, libgtk3.} 465 | proc gtk_text_buffer_get_selection_bounds*(buffer: pointer, start, `end`: var GtkTextIter): bool {.importc, libgtk3.} 466 | proc gtk_text_buffer_select_range*(buffer: pointer, ins, bound: var GtkTextIter) {.importc, libgtk3.} 467 | proc gtk_text_buffer_get_iter_at_offset*(buffer: pointer, iter: var GtkTextIter, char_offset: cint) {.importc, libgtk3.} 468 | proc gtk_text_buffer_get_iter_at_mark*(buffer: pointer, iter: var GtkTextIter, mark: pointer) {.importc, libgtk3.} 469 | proc gtk_text_buffer_create_tag*(buffer: pointer, tag_name, property_name, value: cstring, terminator: pointer): pointer {.importc, libgtk3.} 470 | proc gtk_text_buffer_insert_with_tags_by_name*(buffer: pointer, iter: var GtkTextIter, text: cstring, len: cint, first_tag_name: cstring, terminator: pointer) {.importc, libgtk3.} 471 | 472 | proc gtk_text_iter_get_offset*(iter: var GtkTextIter): cint {.importc, libgtk3.} 473 | # proc gtk_text_mark_new*(name: cstring, left_gravity: bool): pointer {.importc, libgtk3.} 474 | 475 | proc gtk_scrolled_window_new*(hadjustment, vadjustment: pointer): pointer {.importc, libgtk3.} 476 | proc gtk_scrolled_window_set_policy*(scrolled_window: pointer, hscrollbar_policy, vscrollbar_policy: cint) {.importc, libgtk3.} 477 | proc gtk_scrolled_window_get_hscrollbar*(scrolled_window: pointer): pointer {.importc, libgtk3.} 478 | proc gtk_scrolled_window_get_vscrollbar*(scrolled_window: pointer): pointer {.importc, libgtk3.} 479 | proc gtk_scrolled_window_get_hadjustment*(scrolled_window: pointer): pointer {.importc, libgtk3.} 480 | proc gtk_scrolled_window_get_vadjustment*(scrolled_window: pointer): pointer {.importc, libgtk3.} 481 | # proc gtk_scrolled_window_get_max_content_width*(scrolled_window: pointer): cint {.importc, libgtk3.} 482 | # proc gtk_scrolled_window_get_min_content_width*(scrolled_window: pointer): cint {.importc, libgtk3.} 483 | # proc gtk_scrolled_window_set_overlay_scrolling*(scrolled_window: pointer, overlay_scrolling: bool) {.importc, libgtk3.} 484 | 485 | proc gtk_frame_new*(label: cstring): pointer {.importc, libgtk3.} 486 | proc gtk_frame_set_label*(frame: pointer, label: cstring) {.importc, libgtk3.} 487 | proc gtk_frame_get_label_widget*(frame: pointer): pointer {.importc, libgtk3.} 488 | 489 | proc gtk_drawing_area_new*(): pointer {.importc, libgtk3.} 490 | 491 | proc gtk_adjustment_get_value*(adjustment: pointer): cdouble {.importc, libgtk3.} 492 | proc gtk_adjustment_set_value*(adjustment: pointer, value: cdouble) {.importc, libgtk3.} 493 | proc gtk_adjustment_set_upper*(adjustment: pointer, upper: cdouble) {.importc, libgtk3.} 494 | proc gtk_adjustment_get_upper*(adjustment: pointer): cdouble {.importc, libgtk3.} 495 | proc gtk_adjustment_set_page_size*(adjustment: pointer, page_size: cdouble) {.importc, libgtk3.} 496 | proc gtk_adjustment_get_page_size*(adjustment: pointer): cdouble {.importc, libgtk3.} 497 | 498 | proc gtk_drag_dest_set*(widget: pointer, flags: cint, targets: pointer, n_targets: cint, actions: cint) {.importc, libgtk3.} 499 | 500 | proc gdk_keyval_to_unicode*(keyval: cint): cint {.importc, libgtk3.} 501 | 502 | proc gdk_screen_get_default*(): pointer {.importc, libgtk3.} 503 | proc gdk_screen_get_primary_monitor*(screen: pointer): cint {.importc, libgtk3.} 504 | # proc gdk_screen_get_width*(screen: pointer): cint {.importc, libgtk3.} 505 | # proc gdk_screen_get_height*(screen: pointer): cint {.importc, libgtk3.} 506 | proc gdk_screen_get_monitor_workarea*(screen: pointer, monitor_num: cint, dest: var GdkRectangle) {.importc, libgtk3.} 507 | 508 | proc gtk_style_context_get_padding*(context: pointer, state: cint, padding: var GtkBorder) {.importc, libgtk3.} 509 | proc gtk_style_context_get_background_color*(context: pointer, state: cint, color: var GdkRGBA) {.importc, libgtk3.} 510 | proc gtk_style_context_get_color*(context: pointer, state: cint, color: var GdkRGBA) {.importc, libgtk3.} 511 | # proc gtk_style_context_get_font*(context: pointer, state: cint): pointer {.importc, libgtk3.} 512 | 513 | proc gtk_border_new*(): pointer {.importc, libgtk3.} 514 | 515 | proc gdk_threads_add_idle*(function, data: pointer): cint {.importc, libgtk3.} 516 | 517 | proc gtk_scrollbar_new*(orientation: cint, adjustment: pointer): pointer {.importc, libgtk3.} 518 | 519 | proc gtk_adjustment_new*(value, lower, upper, step_increment, page_increment, page_size: cdouble): pointer {.importc, libgtk3.} 520 | 521 | # proc gtk_selection_data_get_length*(selection_data: pointer): cint {.importc, libgtk3.} 522 | # proc gtk_selection_data_get_text*(selection_data: pointer): cstring {.importc, libgtk3.} 523 | 524 | proc gtk_selection_data_get_uris*(selection_data: pointer): ptr cstring {.importc, libgtk3.} 525 | proc g_filename_from_uri*(uri: pointer): cstring {.importc, libgtk3.} 526 | 527 | proc gtk_clipboard_get*(selection: pointer): pointer {.importc, libgtk3.} 528 | proc gtk_clipboard_set_text*(clipboard: pointer, text: cstring, len: cint) {.importc, libgtk3.} 529 | proc gtk_clipboard_request_text*(clipboard, callback, user_data: pointer) {.importc, libgtk3.} 530 | proc gtk_clipboard_store*(clipboard: pointer) {.importc, libgtk3.} 531 | 532 | proc gtk_accelerator_get_default_mod_mask*(): cint {.importc, libgtk3.} 533 | 534 | proc gtk_im_multicontext_new*(): pointer {.importc, libgtk3.} 535 | proc gtk_im_context_filter_keypress*(context: pointer, event: var GdkEventKey): bool {.importc, libgtk3.} 536 | 537 | 538 | # ---------------------------------------------------------------------------------------- 539 | # Drawing Related Procs 540 | # ---------------------------------------------------------------------------------------- 541 | 542 | proc gtk_widget_create_pango_layout*(widget: pointer, text: cstring): pointer {.importc, libgtk3.} 543 | proc gdk_cairo_set_source_rgba*(cr: pointer, rgba: var GdkRGBA) {.importc, libgtk3.} 544 | proc gdk_cairo_surface_create_from_pixbuf*(pixbuf: pointer, scale: cint, for_window: pointer): pointer {.importc, libgtk3.} 545 | proc gdk_pixbuf_new_from_file*(filename: cstring, error: pointer): pointer {.importc, libgtk3.} 546 | proc gdk_pixbuf_save*(pixbuf: pointer, filename, `type`: cstring, error: pointer, param5, param6, param7: cstring): bool {.importc, libgtk3.} 547 | proc gdk_pixbuf_get_from_surface*(surface: pointer, src_x, src_y, width, height: cint): pointer {.importc, libgtk3.} 548 | proc gdk_pixbuf_apply_embedded_orientation*(src: pointer): pointer {.importc, libgtk3.} 549 | # proc gdk_pixmap_create_from_data*(drawable, data: pointer, width, height, depth: cint, fg, bg: var GdkRGBA): pointer {.importc, libgtk3.} 550 | 551 | proc cairo_image_surface_create*(format: cint, width, height: cint): pointer {.importc, libgtk3.} 552 | # proc cairo_image_surface_create_for_data*(data: pointer, format: cairo_format_t, width, height, stride: cint): pointer {.importc, libgtk3.} 553 | proc cairo_image_surface_get_width*(surface: pointer): cint {.importc, libgtk3.} 554 | proc cairo_image_surface_get_height*(surface: pointer): cint {.importc, libgtk3.} 555 | proc cairo_image_surface_get_stride*(surface: pointer): cint {.importc, libgtk3.} 556 | proc cairo_image_surface_get_data*(surface: pointer): ptr UncheckedArray[byte] {.importc, libgtk3.} 557 | proc cairo_surface_flush*(surface: pointer) {.importc, libgtk3.} 558 | proc cairo_surface_mark_dirty*(surface: pointer) {.importc, libgtk3.} 559 | proc cairo_surface_destroy*(surface: pointer) {.importc, libgtk3.} 560 | 561 | # proc cairo_format_stride_for_width*(format: cairo_format_t, width: cint): cint {.importc, libgtk3.} 562 | 563 | proc cairo_create*(target: pointer): pointer {.importc, libgtk3.} 564 | proc cairo_destroy*(cr: pointer) {.importc, libgtk3.} 565 | proc cairo_get_target*(cr: pointer): pointer {.importc, libgtk3.} 566 | proc cairo_set_source_rgb*(cr: pointer, red, green, blue: cdouble) {.importc, libgtk3.} 567 | proc cairo_set_source_surface*(cr, surface: pointer, x, y: cdouble) {.importc, libgtk3.} 568 | proc cairo_get_source*(cr: pointer): pointer {.importc, libgtk3.} 569 | proc cairo_pattern_set_filter*(pattern: pointer, filter: cint) {.importc, libgtk3.} 570 | proc cairo_fill*(cr: pointer) {.importc, libgtk3.} 571 | proc cairo_stroke*(cr: pointer) {.importc, libgtk3.} 572 | proc cairo_rectangle*(cr: pointer, x, y, width, height: cdouble) {.importc, libgtk3.} 573 | proc cairo_arc*(cr: pointer, xc, yc, radius, angle1, angle2: cdouble) {.importc, libgtk3.} 574 | proc cairo_line_to*(cr: pointer, x, y: cdouble) {.importc, libgtk3.} 575 | proc cairo_move_to*(cr: pointer, x, y: cdouble) {.importc, libgtk3.} 576 | proc cairo_set_line_width*(cr: pointer, width: cdouble) {.importc, libgtk3.} 577 | # proc cairo_image_surface_create_from_png*(filename: cstring): pointer {.importc, libgtk3.} 578 | proc cairo_paint*(cr: pointer) {.importc, libgtk3.} 579 | proc cairo_scale*(cr: pointer, x, y: cdouble) {.importc, libgtk3.} 580 | proc cairo_translate*(cr: pointer, tx, ty: cdouble) {.importc, libgtk3.} 581 | # proc cairo_set_antialias*(cr: pointer, antialias: cint) {.importc, libgtk3.} 582 | proc cairo_save*(cr: pointer) {.importc, libgtk3.} 583 | proc cairo_restore*(cr: pointer) {.importc, libgtk3.} 584 | 585 | proc pango_cairo_show_layout*(cr, layout: pointer) {.importc, libgtk3.} 586 | proc pango_cairo_create_layout*(cr: pointer): pointer {.importc, libgtk3.} 587 | proc pango_layout_set_text*(layout: pointer, text: cstring, length: cint) {.importc, libgtk3.} 588 | proc pango_layout_get_pixel_size*(layout: pointer, width, height: var cint) {.importc, libgtk3.} 589 | proc pango_layout_set_font_description*(layout, desc: pointer) {.importc, libgtk3.} 590 | proc pango_font_description_new*(): pointer {.importc, libgtk3.} 591 | proc pango_font_description_free*(desc: pointer) {.importc, libgtk3.} 592 | proc pango_font_description_set_family*(desc: pointer, family: cstring) {.importc, libgtk3.} 593 | proc pango_font_description_set_size*(desc: pointer, size: cint) {.importc, libgtk3.} 594 | proc pango_font_description_set_weight*(desc: pointer, weight: cint) {.importc, libgtk3.} 595 | # proc pango_font_description_get_size*(desc: pointer): cint {.importc, libgtk3.} 596 | # proc pango_layout_set_markup*(layout: pointer, markup: cstring, length: cint) {.importc, libgtk3.} 597 | # proc pango_layout_new*(context: pointer): pointer {.importc, libgtk3.} 598 | -------------------------------------------------------------------------------- /src/nigui/private/gtk3/platform_impl.nim: -------------------------------------------------------------------------------- 1 | # NiGui - GTK+ 3 platform-specific code - part 3 2 | 3 | # This file will be included in "nigui.nim". 4 | 5 | # Imports: 6 | # math, os, strutils, times are imported by nigui.nim 7 | import gtk3 8 | import tables 9 | import hashes 10 | 11 | 12 | # ---------------------------------------------------------------------------------------- 13 | # Internal Things 14 | # ---------------------------------------------------------------------------------------- 15 | 16 | const pFontSizeFactor = 700 17 | 18 | # needed to calculate clicks: 19 | var pLastMouseButtonDownControl: ControlImpl 20 | var pLastMouseButtonDownControlX: int 21 | var pLastMouseButtonDownControlY: int 22 | 23 | var pClipboardPtr: pointer 24 | var pClipboardText: string 25 | var pClipboardTextIsSet: bool 26 | 27 | proc pRaiseGError(error: ptr GError) = 28 | if error == nil: 29 | raiseError("Unknown error") 30 | raiseError($error.message) 31 | 32 | proc pColorToGdkRGBA(color: Color, rgba: var GdkRGBA) = 33 | rgba.red = color.red.float / 255 34 | rgba.green = color.green.float / 255 35 | rgba.blue = color.blue.float / 255 36 | rgba.alpha = color.alpha.float / 255 37 | 38 | proc pGdkRGBAToColor(rgba: var GdkRGBA): Color = 39 | result.red = uint8(rgba.red.float * 255) 40 | result.green = uint8(rgba.green.float * 255) 41 | result.blue = uint8(rgba.blue.float * 255) 42 | result.alpha = uint8(rgba.alpha.float * 255) 43 | 44 | proc pWindowDeleteSignal(widgetHandle, event, data: pointer): Gboolean {.cdecl.} = 45 | let window = cast[WindowImpl](data) 46 | window.closeClick() 47 | return true 48 | 49 | proc pWindowConfigureSignal(windowHandle, event, data: pointer): Gboolean {.cdecl.} = 50 | # called on resize and move 51 | let window = cast[WindowImpl](data) 52 | var x, y: cint 53 | gtk_window_get_position(window.fHandle, x, y) 54 | window.fX = x 55 | window.fY = y 56 | var width, height: cint 57 | gtk_window_get_size(window.fHandle, width, height) 58 | window.fWidth = width 59 | window.fHeight = height 60 | window.fClientWidth = width 61 | window.fClientHeight = height 62 | var event = new ResizeEvent 63 | event.window = window 64 | window.handleResizeEvent(event) 65 | window.triggerRelayout() 66 | 67 | proc pKeyvalToKey(keyval: cint): Key = 68 | result = case keyval 69 | 70 | # the following block is probably only correct for german keyboard layout 71 | of 39: Key_NumberSign 72 | of 42: Key_Plus 73 | of 58: Key_Point 74 | of 59: Key_Comma 75 | of 95: Key_Minus 76 | 77 | of 97: Key_A 78 | of 98: Key_B 79 | of 99: Key_C 80 | of 100: Key_D 81 | of 101: Key_E 82 | of 102: Key_F 83 | of 103: Key_G 84 | of 104: Key_H 85 | of 105: Key_I 86 | of 106: Key_J 87 | of 107: Key_K 88 | of 108: Key_L 89 | of 109: Key_M 90 | of 110: Key_N 91 | of 111: Key_O 92 | of 112: Key_P 93 | of 113: Key_Q 94 | of 114: Key_R 95 | of 115: Key_S 96 | of 116: Key_T 97 | of 117: Key_U 98 | of 118: Key_V 99 | of 119: Key_W 100 | of 120: Key_X 101 | of 121: Key_Y 102 | of 122: Key_Z 103 | of 228: Key_AE 104 | of 246: Key_OE 105 | of 252: Key_UE 106 | of 65027: Key_AltGr 107 | of 65106: Key_Circumflex 108 | of 65288: Key_Backspace 109 | of 65289: Key_Tab 110 | of 65293: Key_Return 111 | of 65299: Key_Pause 112 | of 65300: Key_ScrollLock 113 | of 65307: Key_Escape 114 | of 65379: Key_Insert 115 | of 65360: Key_Home 116 | of 65361: Key_Left 117 | of 65362: Key_Up 118 | of 65363: Key_Right 119 | of 65364: Key_Down 120 | of 65365: Key_PageUp 121 | of 65366: Key_PageDown 122 | of 65367: Key_End 123 | of 65377: Key_Print 124 | of 65383: Key_ContextMenu 125 | of 65407: Key_NumLock 126 | of 65421: Key_NumpadEnter 127 | of 65450: Key_NumpadMultiply 128 | of 65451: Key_NumpadAdd 129 | of 65452: Key_NumpadSeparator 130 | of 65453: Key_NumpadSubtract 131 | of 65454: Key_NumpadDecimal 132 | of 65455: Key_NumpadDivide 133 | of 65456: Key_Numpad0 134 | of 65457: Key_Numpad1 135 | of 65458: Key_Numpad2 136 | of 65459: Key_Numpad3 137 | of 65460: Key_Numpad4 138 | of 65461: Key_Numpad5 139 | of 65462: Key_Numpad6 140 | of 65463: Key_Numpad7 141 | of 65464: Key_Numpad8 142 | of 65465: Key_Numpad9 143 | of 65470: Key_F1 144 | of 65471: Key_F2 145 | of 65472: Key_F3 146 | of 65473: Key_F4 147 | of 65474: Key_F5 148 | of 65475: Key_F6 149 | of 65476: Key_F7 150 | of 65477: Key_F8 151 | of 65478: Key_F9 152 | of 65479: Key_F10 153 | of 65480: Key_F11 154 | of 65481: Key_F12 155 | of 65482: Key_F13 156 | of 65483: Key_F14 157 | of 65484: Key_F15 158 | of 65485: Key_F16 159 | of 65486: Key_F17 160 | of 65487: Key_F18 161 | of 65488: Key_F19 162 | of 65489: Key_F20 163 | of 65490: Key_F21 164 | of 65491: Key_F22 165 | of 65492: Key_F23 166 | of 65493: Key_F24 167 | of 65505: Key_ShiftL 168 | of 65506: Key_ShiftR 169 | of 65507: Key_ControlL 170 | of 65508: Key_ControlR 171 | of 65509: Key_CapsLock 172 | of 65513: Key_AltL 173 | of 65514: Key_AltR 174 | of 65515: Key_SuperL 175 | of 65516: Key_SuperR 176 | of 65535: Key_Delete 177 | else: cast[Key](keyval) 178 | 179 | proc pWindowKeyPressSignal(widget: pointer, event: var GdkEventKey, data: pointer): Gboolean {.cdecl.} = 180 | let window = cast[WindowImpl](data) 181 | window.fKeyPressed = pKeyvalToKey(event.keyval) 182 | internalKeyDown(window.fKeyPressed) 183 | if gtk_im_context_filter_keypress(window.fIMContext, event) and window.fKeyPressed == Key_None: 184 | return 185 | var evt = new KeyboardEvent 186 | evt.window = window 187 | evt.key = window.fKeyPressed 188 | if evt.key == Key_None: 189 | echo "Unknown key value: ", event.keyval 190 | return 191 | evt.character = $event.`string` 192 | evt.unicode = gdk_keyval_to_unicode(event.keyval) 193 | try: 194 | window.handleKeyDownEvent(evt) 195 | except: 196 | handleException() 197 | result = evt.handled 198 | 199 | proc pWindowKeyReleaseSignal(widget: pointer, event: var GdkEventKey): Gboolean {.cdecl.} = 200 | internalKeyUp(pKeyvalToKey(event.keyval)) 201 | 202 | proc pControlKeyPressSignal(widget: pointer, event: var GdkEventKey, data: pointer): Gboolean {.cdecl.} = 203 | let control = cast[ControlImpl](data) 204 | let key = pKeyvalToKey(event.keyval) 205 | control.fKeyPressed = key 206 | if gtk_im_context_filter_keypress(control.fIMContext, event): 207 | return control.fKeyPressed == Key_None 208 | var evt = new KeyboardEvent 209 | evt.control = control 210 | evt.key = key 211 | if evt.key == Key_None: 212 | echo "Unknown key value: ", event.keyval 213 | return 214 | evt.character = $event.`string` 215 | evt.unicode = gdk_keyval_to_unicode(event.keyval) 216 | try: 217 | control.handleKeyDownEvent(evt) 218 | except: 219 | handleException() 220 | result = evt.handled 221 | 222 | proc pWindowIMContextCommitSignal(context: pointer, str: cstring, data: pointer) {.cdecl.} = 223 | let window = cast[WindowImpl](data) 224 | var evt = new KeyboardEvent 225 | evt.window = window 226 | evt.character = $str 227 | evt.unicode = evt.character.runeAt(0).toUpper().int 228 | evt.key = window.fKeyPressed 229 | window.fKeyPressed = Key_None 230 | try: 231 | window.handleKeyDownEvent(evt) 232 | except: 233 | handleException() 234 | 235 | proc pControlIMContextCommitSignal(context: pointer, str: cstring, data: pointer) {.cdecl.} = 236 | let control = cast[ControlImpl](data) 237 | var evt = new KeyboardEvent 238 | evt.control = control 239 | evt.character = $str 240 | evt.unicode = evt.character.runeAt(0).toUpper().int 241 | evt.key = control.fKeyPressed 242 | try: 243 | control.handleKeyDownEvent(evt) 244 | if evt.handled: 245 | control.fKeyPressed = Key_None 246 | except: 247 | handleException() 248 | 249 | method focus(control: ControlImpl) = 250 | gtk_widget_grab_focus(control.fHandle) 251 | 252 | method focus(control: NativeTextArea) = 253 | gtk_widget_grab_focus(control.fTextViewHandle) 254 | 255 | proc pDefaultControlButtonPressSignal(widget: pointer, event: var GdkEventButton, data: pointer): Gboolean {.cdecl.} = 256 | let control = cast[ControlImpl](data) 257 | let x = event.x.int 258 | let y = event.y.int 259 | var evt = new MouseEvent 260 | evt.control = control 261 | case event.button 262 | of 1: evt.button = MouseButton_Left 263 | of 2: evt.button = MouseButton_Middle 264 | of 3: evt.button = MouseButton_Right 265 | else: return # TODO 266 | evt.x = x 267 | evt.y = y 268 | 269 | try: 270 | control.handleMouseButtonDownEvent(evt) 271 | except: 272 | handleException() 273 | 274 | pLastMouseButtonDownControl = control 275 | pLastMouseButtonDownControlX = x 276 | pLastMouseButtonDownControlY = y 277 | 278 | proc pCustomControlButtonPressSignal(widget: pointer, event: var GdkEventButton, data: pointer): Gboolean {.cdecl.} = 279 | discard pDefaultControlButtonPressSignal(widget, event, data) 280 | let control = cast[ControlImpl](data) 281 | control.focus() 282 | result = true # Stop propagation, required to detect clicks 283 | 284 | proc pControlButtonReleaseSignal(widget: pointer, event: var GdkEventButton, data: pointer): Gboolean {.cdecl.} = 285 | let control = cast[ControlImpl](data) 286 | let x = event.x.int 287 | let y = event.y.int 288 | if not (x >= 0 and x < control.width and y >= 0 and y < control.height): 289 | return 290 | var evt = new MouseEvent 291 | evt.control = control 292 | case event.button 293 | of 1: evt.button = MouseButton_Left 294 | of 2: evt.button = MouseButton_Middle 295 | of 3: evt.button = MouseButton_Right 296 | else: return # TODO 297 | evt.x = x 298 | evt.y = y 299 | control.handleMouseButtonUpEvent(evt) 300 | if event.button == 1 and control == pLastMouseButtonDownControl and abs(x - pLastMouseButtonDownControlX) <= clickMaxXYMove and abs(y - pLastMouseButtonDownControlY) <= clickMaxXYMove: 301 | var clickEvent = new ClickEvent 302 | clickEvent.control = control 303 | try: 304 | control.handleClickEvent(clickEvent) 305 | except: 306 | handleException() 307 | # result = true # stop propagation 308 | 309 | proc pControlMotionNotifySignal(widget: pointer, event: var GdkEventMotion, data: pointer): Gboolean {.cdecl.} = 310 | let control = cast[ControlImpl](data) 311 | var evt = new MouseEvent 312 | evt.control = control 313 | evt.x = event.x.int 314 | evt.y = event.y.int 315 | control.handleMouseMoveEvent(evt) 316 | 317 | proc pControlEnterNotifySignal(widget: pointer, event: var GdkEventCrossing, data: pointer): Gboolean {.cdecl.} = 318 | let control = cast[ControlImpl](data) 319 | var evt = new MouseEvent 320 | evt.control = control 321 | control.handleMouseEnterEvent(evt) 322 | 323 | proc pControlLeaveNotifySignal(widget: pointer, event: var GdkEventCrossing, data: pointer): Gboolean {.cdecl.} = 324 | let control = cast[ControlImpl](data) 325 | var evt = new MouseEvent 326 | evt.control = control 327 | control.handleMouseLeaveEvent(evt) 328 | 329 | proc pControlChangedSignal(widget: pointer, data: pointer): Gboolean {.cdecl.} = 330 | let control = cast[TextBox](data) 331 | var evt = new TextChangeEvent 332 | evt.control = control 333 | try: 334 | control.handleTextChangeEvent(evt) 335 | except: 336 | handleException() 337 | 338 | # proc pTextAreaEndUserActionSignal(widget: pointer, data: pointer): Gboolean {.cdecl.} = 339 | # let control = cast[ControlImpl](data) 340 | # discard 341 | 342 | proc pSetDragDest(widget: pointer) = 343 | var target: GtkTargetEntry 344 | target.target = "text/uri-list" 345 | gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, target.addr, 1, GDK_ACTION_COPY) 346 | 347 | proc pCreateFont(fontFamily: string, fontSize: float, fontBold: bool): pointer = 348 | result = pango_font_description_new() 349 | pango_font_description_set_family(result, fontFamily) 350 | pango_font_description_set_size(result, cint(fontSize * pFontSizeFactor)) 351 | if fontBold: 352 | pango_font_description_set_weight(result, 700) 353 | else: 354 | pango_font_description_set_weight(result, 400) 355 | 356 | 357 | # ---------------------------------------------------------------------------------------- 358 | # App Procedures 359 | # ---------------------------------------------------------------------------------------- 360 | 361 | var pQueue: int 362 | 363 | proc init(app: App) = 364 | gtk_init(nil, nil) 365 | 366 | pClipboardPtr = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD) 367 | 368 | # Determine default styles: 369 | var window = gtk_window_new(GTK_WINDOW_TOPLEVEL) 370 | var context = gtk_widget_get_style_context(window) 371 | var rgba: GdkRGBA 372 | gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, rgba) 373 | app.defaultBackgroundColor = rgba.pGdkRGBAToColor() 374 | gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, rgba) 375 | app.defaultTextColor = rgba.pGdkRGBAToColor() 376 | gtk_widget_destroy(window) 377 | 378 | proc runMainLoop() = gtk_main() 379 | 380 | proc platformQuit() = 381 | gtk_main_quit() 382 | 383 | proc processEvents(app: App) = 384 | while gtk_events_pending() == 1: 385 | discard gtk_main_iteration() 386 | 387 | proc runQueuedFn(data: pointer): cint {.exportc.} = 388 | var fn = cast[ptr proc()](data) 389 | fn[]() 390 | freeShared(fn) 391 | dec pQueue 392 | return 0 393 | 394 | proc queueMain(app: App, fn: proc()) = 395 | inc pQueue 396 | var p = createShared(proc()) 397 | p[] = fn 398 | discard gdk_threads_add_idle(cast[pointer](runQueuedFn), p) 399 | 400 | proc queued(app: App): int = 401 | return pQueue 402 | 403 | proc pClipboardTextReceivedFunc(clipboard: pointer, text: cstring, data: pointer): Gboolean {.cdecl.} = 404 | pClipboardText = $text # string needs to be copied 405 | pClipboardTextIsSet = true 406 | 407 | proc clipboardText(app: App): string = 408 | pClipboardTextIsSet = false 409 | gtk_clipboard_request_text(pClipboardPtr, cast[pointer](pClipboardTextReceivedFunc), nil) 410 | while not pClipboardTextIsSet: 411 | discard gtk_main_iteration() 412 | result = pClipboardText 413 | 414 | proc `clipboardText=`(app: App, text: string) = 415 | gtk_clipboard_set_text(pClipboardPtr, text, text.len.cint) 416 | gtk_clipboard_store(pClipboardPtr) 417 | 418 | 419 | # ---------------------------------------------------------------------------------------- 420 | # Dialogs 421 | # ---------------------------------------------------------------------------------------- 422 | 423 | proc alert(window: Window, message: string, title = "Message") = 424 | var dialog = gtk_dialog_new() 425 | gtk_window_set_title(dialog, title) 426 | gtk_window_resize(dialog, 200, 70) 427 | let contentArea = gtk_dialog_get_content_area(dialog) 428 | gtk_container_set_border_width(contentArea, 15) 429 | var label = gtk_label_new(message) 430 | gtk_widget_show(label) 431 | gtk_box_pack_start(contentArea, label, 0, 0, 0) 432 | let actionArea = gtk_dialog_get_action_area(dialog) 433 | gtk_button_box_set_layout(actionArea, GTK_BUTTONBOX_EXPAND) 434 | gtk_widget_set_margin_top(actionArea, 15) 435 | discard gtk_dialog_add_button(dialog, "OK", 1) 436 | if window != nil: 437 | gtk_window_set_transient_for(dialog, cast[WindowImpl](window).fHandle) 438 | discard gtk_dialog_run(dialog) 439 | gtk_widget_hide(dialog) 440 | 441 | method run*(dialog: OpenFileDialog) = 442 | dialog.files = @[] 443 | var chooser = gtk_file_chooser_dialog_new(dialog.title.cstring, nil, GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, nil) 444 | discard gtk_file_chooser_set_current_folder(chooser, dialog.directory.cstring) 445 | gtk_file_chooser_set_select_multiple(chooser, dialog.multiple) 446 | let res = gtk_dialog_run(chooser) 447 | if res == GTK_RESPONSE_ACCEPT: 448 | let list = gtk_file_chooser_get_filenames(chooser) 449 | let count = g_slist_length(list) 450 | for i in 0..count - 1: 451 | dialog.files.add($g_slist_nth_data(list, i)) 452 | gtk_widget_destroy(chooser) 453 | 454 | method run(dialog: SaveFileDialog) = 455 | var chooser = gtk_file_chooser_dialog_new(dialog.title.cstring, nil, GTK_FILE_CHOOSER_ACTION_SAVE, "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, nil) 456 | let res = gtk_dialog_run(chooser) 457 | discard gtk_file_chooser_set_current_folder(chooser, dialog.directory.cstring) 458 | if dialog.defaultName.len > 0: 459 | discard gtk_file_chooser_set_current_name(chooser, dialog.defaultName.cstring) # Issue: does not work 460 | if res == GTK_RESPONSE_ACCEPT: 461 | dialog.file = $gtk_file_chooser_get_filename(chooser) 462 | else: 463 | dialog.file = "" 464 | gtk_widget_destroy(chooser) 465 | 466 | method run*(dialog: SelectDirectoryDialog) = 467 | dialog.selectedDirectory = "" 468 | var chooser = gtk_file_chooser_dialog_new(dialog.title.cstring, nil, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "Cancel", GTK_RESPONSE_CANCEL, "Select", GTK_RESPONSE_ACCEPT, nil) 469 | discard gtk_file_chooser_set_current_folder(chooser, dialog.startDirectory.cstring) 470 | let res = gtk_dialog_run(chooser) 471 | if res == GTK_RESPONSE_ACCEPT: 472 | dialog.selectedDirectory = $gtk_file_chooser_get_filename(chooser) 473 | gtk_widget_destroy(chooser) 474 | 475 | 476 | # ---------------------------------------------------------------------------------------- 477 | # Timers 478 | # ---------------------------------------------------------------------------------------- 479 | 480 | type TimerEntry = object 481 | timerInternalId: cint 482 | timerProc: TimerProc 483 | data: pointer 484 | 485 | var 486 | pTimers = initTable[int64, TimerEntry]() 487 | pNextTimerId: int = 1 488 | 489 | proc pTimerFunction(timer: Timer): Gboolean {.cdecl.} = 490 | let timerEntry = pTimers.getOrDefault(cast[int](timer)) 491 | var event = new TimerEvent 492 | event.timer = timer 493 | event.data = timerEntry.data 494 | timerEntry.timerProc(event) 495 | pTimers.del(cast[int](timer)) 496 | # result is false to stop timer 497 | 498 | proc pRepeatingTimerFunction(timer: Timer): Gboolean {.cdecl.} = 499 | let timerEntry = pTimers.getOrDefault(cast[int](timer)) 500 | var event = new TimerEvent 501 | event.timer = timer 502 | event.data = timerEntry.data 503 | timerEntry.timerProc(event) 504 | result = true # repeat timer 505 | 506 | proc startTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer = 507 | var timerEntry: TimerEntry 508 | timerEntry.timerInternalId = g_timeout_add(milliSeconds.cint, cast[pointer](pTimerFunction), cast[pointer](pNextTimerId)) 509 | timerEntry.timerProc = timerProc 510 | timerEntry.data = data 511 | pTimers[pNextTimerId] = timerEntry 512 | result = cast[Timer](pNextTimerId) 513 | pNextTimerId.inc() 514 | if pNextTimerId == inactiveTimer: 515 | pNextTimerId.inc() 516 | 517 | proc startRepeatingTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer = 518 | var timerEntry: TimerEntry 519 | timerEntry.timerInternalId = g_timeout_add(milliSeconds.cint, cast[pointer](pRepeatingTimerFunction), cast[pointer](pNextTimerId)) 520 | timerEntry.timerProc = timerProc 521 | timerEntry.data = data 522 | pTimers[pNextTimerId] = timerEntry 523 | result = cast[Timer](pNextTimerId) 524 | pNextTimerId.inc() 525 | if pNextTimerId == inactiveTimer: 526 | pNextTimerId.inc() 527 | 528 | proc stop(timer: var Timer) = 529 | if cast[int](timer) != inactiveTimer: 530 | let timerEntry = pTimers.getOrDefault(cast[int](timer)) 531 | pTimers.del(cast[int](timer)) 532 | discard g_source_remove(timerEntry.timerInternalId) 533 | timer = cast[Timer](inactiveTimer) 534 | 535 | 536 | # ---------------------------------------------------------------------------------------- 537 | # Canvas 538 | # ---------------------------------------------------------------------------------------- 539 | 540 | proc pUpdateFont(canvas: Canvas) = 541 | let canvasImpl = cast[CanvasImpl](canvas) 542 | if canvasImpl.fFont != nil: 543 | pango_font_description_free(canvasImpl.fFont) 544 | canvasImpl.fFont = pCreateFont(canvas.fontFamily, canvas.fontSize, canvas.fontBold) 545 | 546 | method drawText(canvas: Canvas, text: string, x, y = 0) = 547 | let canvasImpl = cast[CanvasImpl](canvas) 548 | let cr = canvasImpl.fCairoContext 549 | if cr == nil: 550 | raiseError("Canvas is not in drawing state.") 551 | var rgba: GdkRGBA 552 | canvas.textColor.pColorToGdkRGBA(rgba) 553 | gdk_cairo_set_source_rgba(cr, rgba) 554 | 555 | var layout = pango_cairo_create_layout(cr) 556 | pango_layout_set_text(layout, text, text.len.cint) 557 | 558 | if canvasImpl.fFont == nil: 559 | canvas.pUpdateFont() 560 | pango_layout_set_font_description(layout, canvasImpl.fFont) 561 | 562 | cairo_save(cr) 563 | cairo_translate(cr, x.float, y.float) 564 | pango_cairo_show_layout(cr, layout) 565 | cairo_restore(cr) 566 | g_object_unref(layout) 567 | 568 | method drawLine(canvas: Canvas, x1, y1, x2, y2: int) = 569 | let cr = cast[CanvasImpl](canvas).fCairoContext 570 | if cr == nil: 571 | raiseError("Canvas is not in drawing state.") 572 | var rgba: GdkRGBA 573 | canvas.lineColor.pColorToGdkRGBA(rgba) 574 | gdk_cairo_set_source_rgba(cr, rgba) 575 | cairo_move_to(cr, x1.float, y1.float) 576 | cairo_line_to(cr, x2.float, y2.float) 577 | cairo_set_line_width(cr, canvas.lineWidth) 578 | cairo_stroke(cr) 579 | 580 | method drawRectArea(canvas: Canvas, x, y, width, height: int) = 581 | let cr = cast[CanvasImpl](canvas).fCairoContext 582 | if cr == nil: 583 | raiseError("Canvas is not in drawing state.") 584 | var rgba: GdkRGBA 585 | canvas.areaColor.pColorToGdkRGBA(rgba) 586 | gdk_cairo_set_source_rgba(cr, rgba) 587 | cairo_rectangle(cr, x.float, y.float, width.float, height.float) 588 | cairo_fill(cr) 589 | 590 | method drawRectOutline(canvas: Canvas, x, y, width, height: int) = 591 | let cr = cast[CanvasImpl](canvas).fCairoContext 592 | if cr == nil: 593 | raiseError("Canvas is not in drawing state.") 594 | var rgba: GdkRGBA 595 | canvas.lineColor.pColorToGdkRGBA(rgba) 596 | gdk_cairo_set_source_rgba(cr, rgba) 597 | cairo_rectangle(cr, x.float, y.float, width.float, height.float) 598 | cairo_set_line_width(cr, canvas.lineWidth) 599 | cairo_stroke(cr) 600 | 601 | method drawEllipseArea(canvas: Canvas, x, y, width, height: int) = 602 | let cr = cast[CanvasImpl](canvas).fCairoContext 603 | if cr == nil: 604 | raiseError("Canvas is not in drawing state.") 605 | var rgba: GdkRGBA 606 | canvas.areaColor.pColorToGdkRGBA(rgba) 607 | gdk_cairo_set_source_rgba(cr, rgba) 608 | cairo_save(cr) 609 | let centerX = x.float + width.float / 2 610 | let centerY = y.float + height.float / 2 611 | cairo_translate(cr, centerX, centerY) 612 | cairo_scale(cr, width.float / 2, height.float / 2) 613 | cairo_arc(cr, 0, 0, 1, 0, 2 * PI) 614 | cairo_fill(cr) 615 | cairo_restore(cr) 616 | 617 | method drawEllipseOutline(canvas: Canvas, x, y, width, height: int) = 618 | let cr = cast[CanvasImpl](canvas).fCairoContext 619 | if cr == nil: 620 | raiseError("Canvas is not in drawing state.") 621 | var rgba: GdkRGBA 622 | canvas.lineColor.pColorToGdkRGBA(rgba) 623 | gdk_cairo_set_source_rgba(cr, rgba) 624 | cairo_save(cr) 625 | let centerX = x.float + width.float / 2 626 | let centerY = y.float + height.float / 2 627 | cairo_translate(cr, centerX, centerY) 628 | cairo_scale(cr, width.float / 2, height.float / 2) 629 | cairo_arc(cr, 0, 0, 1, 0, 2 * PI) 630 | cairo_set_line_width(cr, canvas.lineWidth / width.float * 2) 631 | # problem: width of horizontal line and vertical line is not the same 632 | cairo_stroke(cr) 633 | cairo_restore(cr) 634 | 635 | method drawArcOutline(canvas: Canvas, centerX, centerY: int, radius, startAngle, sweepAngle: float) = 636 | let cr = cast[CanvasImpl](canvas).fCairoContext 637 | if cr == nil: 638 | raiseError("Canvas is not in drawing state.") 639 | var rgba: GdkRGBA 640 | canvas.lineColor.pColorToGdkRGBA(rgba) 641 | gdk_cairo_set_source_rgba(cr, rgba) 642 | cairo_arc(cr, centerX.float, centerY.float, radius, startAngle, sweepAngle) 643 | cairo_set_line_width(cr, canvas.lineWidth) 644 | cairo_stroke(cr) 645 | 646 | method drawImage(canvas: Canvas, image: Image, x, y = 0, width, height = -1) = 647 | let cr = cast[CanvasImpl](canvas).fCairoContext 648 | if cr == nil: 649 | raiseError("Canvas is not in drawing state.") 650 | let imageCanvas = cast[CanvasImpl](image.canvas) 651 | if imageCanvas.fSurface == nil: 652 | raiseError("Image is not initialized.") 653 | var drawWith = image.width 654 | var drawHeight = image.height 655 | if width != -1: 656 | drawWith = width 657 | drawHeight = int(drawHeight * drawWith / image.width) 658 | if height != -1: 659 | drawHeight = height 660 | if drawWith == image.width and drawHeight == image.height: 661 | cairo_set_source_surface(cr, imageCanvas.fSurface, x.cdouble, y.cdouble) 662 | cairo_paint(cr) 663 | else: 664 | cairo_save(cr) 665 | cairo_translate(cr, x.cdouble, y.cdouble) 666 | cairo_scale(cr, drawWith / image.width, drawHeight / image.height) 667 | cairo_set_source_surface(cr, imageCanvas.fSurface, 0, 0) 668 | case canvas.interpolationMode: 669 | of InterpolationMode_Default: discard 670 | of InterpolationMode_NearestNeighbor: cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST) 671 | of InterpolationMode_Bilinear: cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR) 672 | cairo_paint(cr) 673 | cairo_restore(cr) 674 | 675 | method setPixel(canvas: Canvas, x, y: int, color: Color) = 676 | let canvasImpl = cast[CanvasImpl](canvas) 677 | let cr = canvasImpl.fCairoContext 678 | if cr == nil: 679 | raiseError("Canvas is not in drawing state.") 680 | if canvasImpl.fData == nil: 681 | # For a Canvas of a Control we can't access the memory directly, so draw a rectangle (slower) 682 | var rgba: GdkRGBA 683 | color.pColorToGdkRGBA(rgba) 684 | gdk_cairo_set_source_rgba(cr, rgba) 685 | cairo_rectangle(cr, x.float, y.float, 1, 1) 686 | cairo_fill(cr) 687 | else: 688 | # For a Canvas of an Image we can write in the memory directly (faster) 689 | if x < 0 or y < 0 or x >= canvas.width or y >= canvas.height: 690 | raiseError("Pixel is out of range.") 691 | cairo_surface_flush(canvasImpl.fSurface) 692 | let i = y * canvasImpl.fStride + x * 4 693 | canvasImpl.fData[i + 0] = color.blue 694 | canvasImpl.fData[i + 1] = color.green 695 | canvasImpl.fData[i + 2] = color.red 696 | canvasImpl.fData[i + 3] = 255 697 | cairo_surface_mark_dirty(canvasImpl.fSurface) 698 | 699 | method `fontFamily=`(canvas: CanvasImpl, fontFamily: string) = 700 | procCall canvas.Canvas.`fontFamily=`(fontFamily) 701 | canvas.fFont = nil 702 | 703 | method `fontSize=`(canvas: CanvasImpl, fontSize: float) = 704 | procCall canvas.Canvas.`fontSize=`(fontSize) 705 | canvas.fFont = nil 706 | 707 | method `fontBold=`(canvas: CanvasImpl, fontBold: bool) = 708 | procCall canvas.Canvas.`fontBold=`(fontBold) 709 | canvas.fFont = nil 710 | 711 | method getTextLineWidth(canvas: CanvasImpl, text: string): int = 712 | if canvas.fCairoContext == nil: 713 | raiseError("Canvas is not in drawing state.") 714 | var layout = pango_cairo_create_layout(canvas.fCairoContext) 715 | pango_layout_set_text(layout, text, text.len.cint) 716 | if canvas.fFont == nil: 717 | canvas.pUpdateFont() 718 | pango_layout_set_font_description(layout, canvas.fFont) 719 | var width: cint 720 | var height: cint 721 | pango_layout_get_pixel_size(layout, width, height) 722 | result = width + 2 723 | g_object_unref(layout) 724 | 725 | method getTextLineHeight(canvas: CanvasImpl): int = 726 | if canvas.fCairoContext == nil: 727 | raiseError("Canvas is not in drawing state.") 728 | var layout = pango_cairo_create_layout(canvas.fCairoContext) 729 | pango_layout_set_text(layout, "a", 1) 730 | if canvas.fFont == nil: 731 | canvas.pUpdateFont() 732 | pango_layout_set_font_description(layout, canvas.fFont) 733 | var width: cint 734 | var height: cint 735 | pango_layout_get_pixel_size(layout, width, height) 736 | result = height 737 | g_object_unref(layout) 738 | 739 | 740 | # ---------------------------------------------------------------------------------------- 741 | # Image 742 | # ---------------------------------------------------------------------------------------- 743 | 744 | method resize(image: Image, width, height: int) = 745 | let canvas = cast[CanvasImpl](image.fCanvas) 746 | if canvas.fSurface != nil: 747 | cairo_surface_destroy(canvas.fSurface) 748 | if canvas.fCairoContext != nil: 749 | cairo_destroy(canvas.fCairoContext) 750 | canvas.fSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width.cint, height.cint) 751 | canvas.fCairoContext = cairo_create(canvas.fSurface) 752 | canvas.fData = cairo_image_surface_get_data(canvas.fSurface) 753 | canvas.fStride = cairo_image_surface_get_stride(canvas.fSurface) 754 | image.canvas.fWidth = width 755 | image.canvas.fHeight = height 756 | 757 | method loadFromFile(image: Image, filePath: string) = 758 | let canvas = cast[CanvasImpl](image.fCanvas) 759 | if canvas.fSurface != nil: 760 | cairo_surface_destroy(canvas.fSurface) 761 | if canvas.fCairoContext != nil: 762 | cairo_destroy(canvas.fCairoContext) 763 | image.canvas.fWidth = 0 764 | image.canvas.fHeight = 0 765 | var error: ptr GError 766 | var pixbuf = gdk_pixbuf_new_from_file(filePath, error.addr) 767 | if pixbuf == nil: 768 | pRaiseGError(error) 769 | defer: g_object_unref(pixbuf) 770 | var pixbufRotated = gdk_pixbuf_apply_embedded_orientation(pixbuf) 771 | defer: g_object_unref(pixbufRotated) 772 | canvas.fSurface = gdk_cairo_surface_create_from_pixbuf(pixbufRotated, 1, nil) 773 | canvas.fCairoContext = cairo_create(canvas.fSurface) 774 | canvas.fData = cairo_image_surface_get_data(canvas.fSurface) 775 | canvas.fStride = cairo_image_surface_get_stride(canvas.fSurface) 776 | image.canvas.fWidth = cairo_image_surface_get_width(canvas.fSurface) 777 | image.canvas.fHeight = cairo_image_surface_get_height(canvas.fSurface) 778 | 779 | method saveToBitmapFile(image: Image, filePath: string) = 780 | let canvas = cast[CanvasImpl](image.fCanvas) 781 | var pixbuf = gdk_pixbuf_get_from_surface(canvas.fSurface, 0, 0, image.width.cint, image.height.cint) 782 | defer: g_object_unref(pixbuf) 783 | var error: ptr GError 784 | if not gdk_pixbuf_save(pixbuf, filePath, "bmp", error.addr, nil, nil, nil): 785 | pRaiseGError(error) 786 | 787 | method saveToPngFile(image: Image, filePath: string) = 788 | let canvas = cast[CanvasImpl](image.fCanvas) 789 | var pixbuf = gdk_pixbuf_get_from_surface(canvas.fSurface, 0, 0, image.width.cint, image.height.cint) 790 | defer: g_object_unref(pixbuf) 791 | var error: ptr GError 792 | if not gdk_pixbuf_save(pixbuf, filePath, "png", error.addr, nil, nil, nil): 793 | pRaiseGError(error) 794 | 795 | method saveToJpegFile(image: Image, filePath: string, quality = 80) = 796 | let canvas = cast[CanvasImpl](image.fCanvas) 797 | var pixbuf = gdk_pixbuf_get_from_surface(canvas.fSurface, 0, 0, image.width.cint, image.height.cint) 798 | defer: g_object_unref(pixbuf) 799 | var error: ptr GError 800 | if not gdk_pixbuf_save(pixbuf, filePath, "jpeg", error.addr, "quality", ($quality).cstring, nil): 801 | pRaiseGError(error) 802 | 803 | method beginPixelDataAccess(image: Image): ptr UncheckedArray[byte] = 804 | let canvas = cast[CanvasImpl](image.canvas) 805 | result = canvas.fData 806 | 807 | method endPixelDataAccess(image: Image) = 808 | let canvas = cast[CanvasImpl](image.canvas) 809 | cairo_surface_mark_dirty(canvas.fSurface) 810 | 811 | 812 | # ---------------------------------------------------------------------------------------- 813 | # Window 814 | # ---------------------------------------------------------------------------------------- 815 | 816 | proc pWindowDragDataReceivedSignal(widget, context: pointer, x, y: cint, data: pointer, info, time: cint, user_data: pointer) {.cdecl.} = 817 | let window = cast[WindowImpl](user_data) 818 | var files: seq[string] = @[] 819 | var p = gtk_selection_data_get_uris(data) 820 | while p[] != nil: 821 | files.add($g_filename_from_uri(p[])) 822 | p = cast[ptr cstring](cast[int](p) + 8) 823 | var event = new DropFilesEvent 824 | event.window = window 825 | event.files = files 826 | window.handleDropFilesEvent(event) 827 | 828 | proc pMainScrollbarDraw(widget: pointer, cr: pointer, data: pointer): Gboolean {.cdecl.} = 829 | # This proc is there to get the scrollbar size 830 | if fScrollbarSize == -1: 831 | var scrollbar = gtk_scrolled_window_get_hscrollbar(widget) 832 | var allocation: GdkRectangle 833 | gtk_widget_get_allocation(scrollbar, allocation) 834 | gtk_scrolled_window_set_policy(widget, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC) 835 | fScrollbarSize = allocation.height 836 | for window in windowList: 837 | if window.control != nil: 838 | window.control.triggerRelayoutDownwards() 839 | 840 | proc pWindowStateEventSignal(widget: pointer, event: var GdkEventWindowState, user_data: pointer): Gboolean {.cdecl.} = 841 | let window = cast[WindowImpl](user_data) 842 | window.fMinimized = (event.new_window_state and GDK_WINDOW_STATE_ICONIFIED) == GDK_WINDOW_STATE_ICONIFIED 843 | 844 | proc pWindowFocusOutEventSignal(widget: pointer, event: var GdkEventFocus, user_data: pointer): Gboolean {.cdecl.} = 845 | internalAllKeysUp() 846 | 847 | proc init(window: WindowImpl) = 848 | if pClipboardPtr == nil: 849 | gtk_init(nil, nil) 850 | raiseError("You need to call 'app.init()' at first.") 851 | window.fHandle = gtk_window_new(GTK_WINDOW_TOPLEVEL) 852 | window.fInnerHandle = gtk_scrolled_window_new(nil, nil) 853 | gtk_widget_show(window.fInnerHandle) 854 | gtk_container_add(window.fHandle, window.fInnerHandle) 855 | window.Window.init() 856 | discard g_signal_connect_data(window.fHandle, "delete-event", cast[pointer](pWindowDeleteSignal), cast[pointer](window)) 857 | discard g_signal_connect_data(window.fHandle, "configure-event", cast[pointer](pWindowConfigureSignal), cast[pointer](window)) 858 | discard g_signal_connect_data(window.fHandle, "key-press-event", cast[pointer](pWindowKeyPressSignal), cast[pointer](window)) 859 | discard g_signal_connect_data(window.fHandle, "key-release-event", cast[pointer](pWindowKeyReleaseSignal), cast[pointer](window)) 860 | discard g_signal_connect_data(window.fHandle, "window-state-event", cast[pointer](pWindowStateEventSignal), cast[pointer](window)) 861 | discard g_signal_connect_data(window.fHandle, "focus-out-event", cast[pointer](pWindowFocusOutEventSignal), cast[pointer](window)) 862 | 863 | # Enable drag and drop of files: 864 | pSetDragDest(window.fHandle) 865 | discard g_signal_connect_data(window.fHandle, "drag-data-received", cast[pointer](pWindowDragDataReceivedSignal), cast[pointer](window)) 866 | 867 | if fScrollbarSize == -1: 868 | gtk_scrolled_window_set_policy(window.fInnerHandle, GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS) 869 | discard g_signal_connect_data(window.fInnerHandle, "draw", cast[pointer](pMainScrollbarDraw), nil) 870 | 871 | window.fIMContext = gtk_im_multicontext_new() 872 | discard g_signal_connect_data(window.fIMContext, "commit", cast[pointer](pWindowIMContextCommitSignal), cast[pointer](window)) 873 | 874 | method destroy(window: WindowImpl) = 875 | procCall window.Window.destroy() 876 | gtk_widget_destroy(window.fHandle) 877 | # this destroys also child widgets 878 | window.fHandle = nil 879 | 880 | method `visible=`(window: WindowImpl, visible: bool) = 881 | procCall window.Window.`visible=`(visible) 882 | if visible: 883 | # gtk_window_deiconify(window.fHandle) 884 | # gtk_widget_show(window.fHandle) 885 | gtk_window_present(window.fHandle) 886 | while fScrollbarSize == -1: 887 | discard gtk_main_iteration() 888 | else: 889 | gtk_widget_hide(window.fHandle) 890 | app.processEvents() 891 | 892 | method showModal(window: WindowImpl, parent: Window) = 893 | # Overwrite base method 894 | gtk_window_set_modal(window.fHandle, 1) 895 | gtk_window_set_transient_for(window.fHandle, cast[WindowImpl](parent).fHandle) 896 | gtk_window_set_type_hint(window.fHandle, GDK_WINDOW_TYPE_HINT_DIALOG) 897 | window.visible = true 898 | 899 | method minimize(window: WindowImpl) = 900 | procCall window.Window.minimize() 901 | gtk_window_iconify(window.fHandle) 902 | 903 | method `alwaysOnTop=`(window: WindowImpl, alwaysOnTop: bool) = 904 | procCall window.Window.`alwaysOnTop=`(alwaysOnTop) 905 | gtk_window_set_keep_above(window.fHandle, alwaysOnTop) 906 | 907 | method `width=`*(window: WindowImpl, width: int) = 908 | procCall window.Window.`width=`(width) 909 | gtk_window_set_default_size(window.fHandle, window.width.cint, window.height.cint) 910 | window.fClientWidth = window.width 911 | 912 | method `height=`*(window: WindowImpl, height: int) = 913 | procCall window.Window.`height=`(height) 914 | gtk_window_set_default_size(window.fHandle, window.width.cint, window.height.cint) 915 | window.fClientHeight = window.height 916 | 917 | proc pUpdateMinSize(window: WindowImpl) = 918 | var geometry: GdkGeometry 919 | geometry.min_width = window.minWidth.cint 920 | geometry.min_height = window.minHeight.cint 921 | gtk_window_set_geometry_hints(window.fHandle, nil, geometry, GDK_HINT_MIN_SIZE) 922 | 923 | method `minWidth=`(window: WindowImpl, minWidth: int) = 924 | procCall window.Window.`minWidth=`(minWidth) 925 | pUpdateMinSize(window) 926 | 927 | method `minHeight=`(window: WindowImpl, minHeight: int) = 928 | procCall window.Window.`minHeight=`(minHeight) 929 | pUpdateMinSize(window) 930 | 931 | method `resizable=`(window: WindowImpl, resizable: bool) = 932 | procCall window.Window.`resizable=`(resizable) 933 | gtk_window_set_resizable(window.fHandle, resizable) 934 | 935 | proc pUpdatePosition(window: WindowImpl) = gtk_window_move(window.fHandle, window.x.cint, window.y.cint) 936 | 937 | method `x=`(window: WindowImpl, x: int) = 938 | procCall window.Window.`x=`(x) 939 | window.pUpdatePosition() 940 | 941 | method `y=`(window: WindowImpl, y: int) = 942 | procCall window.Window.`y=`(y) 943 | window.pUpdatePosition() 944 | 945 | method centerOnScreen(window: WindowImpl) = 946 | let screen = gdk_screen_get_default() 947 | let monitor = gdk_screen_get_primary_monitor(screen) 948 | var rect: GdkRectangle 949 | gdk_screen_get_monitor_workarea(screen, monitor, rect) 950 | window.fX = rect.x + (rect.width - window.width) div 2 951 | window.fY = rect.y + (rect.height - window.height) div 2 952 | window.pUpdatePosition() 953 | 954 | method `title=`(window: WindowImpl, title: string) = 955 | procCall window.Window.`title=`(title) 956 | gtk_window_set_title(window.fHandle, window.title.cstring) 957 | 958 | method `control=`(window: WindowImpl, control: Control) = 959 | # Overwrite base method 960 | procCall window.Window.`control=`(control) 961 | gtk_container_add(window.fInnerHandle, cast[ControlImpl](control).fHandle) 962 | 963 | method mousePosition(window: Window): tuple[x, y: int] = 964 | var x, y: cint 965 | gtk_widget_get_pointer(cast[WindowImpl](window).fHandle, x, y) 966 | result.x = x 967 | result.y = y 968 | 969 | method `iconPath=`(window: WindowImpl, iconPath: string) = 970 | procCall window.Window.`iconPath=`(iconPath) 971 | if not gtk_window_set_icon_from_file(window.fHandle, iconPath, nil): 972 | if not fileExists(iconPath): 973 | raiseError("Failed to load image from file '" & iconPath & "': File does not exist") 974 | else: 975 | raiseError("Failed to load image from file '" & iconPath & "'") 976 | 977 | 978 | # ---------------------------------------------------------------------------------------- 979 | # Control 980 | # ---------------------------------------------------------------------------------------- 981 | 982 | method pUpdateScrollBar(control: ControlImpl) {.base.} 983 | 984 | method triggerRelayout(control: ControlImpl) = 985 | procCall control.Control.triggerRelayout() 986 | control.pUpdateScrollBar() 987 | 988 | proc pControlDrawSignal(widget: pointer, cr: pointer, data: pointer): Gboolean {.cdecl.} = 989 | let control = cast[ControlImpl](data) 990 | var event = new DrawEvent 991 | event.control = control 992 | var canvas = cast[CanvasImpl](control.canvas) 993 | if canvas == nil: 994 | canvas = newCanvas(control) 995 | canvas.fCairoContext = cr 996 | try: 997 | control.handleDrawEvent(event) 998 | except: 999 | handleException() 1000 | canvas.fCairoContext = nil 1001 | 1002 | proc pControlScollXSignal(adjustment: pointer, data: pointer) {.cdecl.} = 1003 | let control = cast[ControlImpl](data) 1004 | control.fXScrollPos = gtk_adjustment_get_value(adjustment).int 1005 | control.forceRedraw() 1006 | 1007 | proc pControlScollYSignal(adjustment: pointer, data: pointer) {.cdecl.} = 1008 | let control = cast[ControlImpl](data) 1009 | control.fYScrollPos = gtk_adjustment_get_value(adjustment).int 1010 | control.forceRedraw() 1011 | 1012 | proc pUpdateFont(control: ControlImpl) = 1013 | var font = pCreateFont(control.fontFamily, control.fontSize, control.fontBold) 1014 | gtk_widget_modify_font(control.fHandle, font) 1015 | pango_font_description_free(font) 1016 | 1017 | method pAddButtonPressEvent(control: ControlImpl) {.base.} = 1018 | gtk_widget_add_events(control.fHandle, GDK_BUTTON_PRESS_MASK) 1019 | discard g_signal_connect_data(control.fHandle, "button-press-event", cast[pointer](pCustomControlButtonPressSignal), cast[pointer](control)) 1020 | 1021 | method pAddKeyPressEvent(control: ControlImpl) {.base.} = 1022 | discard g_signal_connect_data(control.fHandle, "key-press-event", cast[pointer](pControlKeyPressSignal), cast[pointer](control)) 1023 | 1024 | proc init(control: ControlImpl) = 1025 | 1026 | if control.fHandle == nil: 1027 | # Direct instance of ControlImpl: 1028 | control.fHandle = gtk_layout_new(nil, nil) 1029 | discard g_signal_connect_data(control.fHandle, "draw", cast[pointer](pControlDrawSignal), cast[pointer](control)) 1030 | gtk_widget_add_events(control.fHandle, GDK_KEY_PRESS_MASK) 1031 | 1032 | control.pAddButtonPressEvent() 1033 | control.pAddKeyPressEvent() 1034 | 1035 | gtk_widget_add_events(control.fHandle, GDK_BUTTON_RELEASE_MASK) 1036 | discard g_signal_connect_data(control.fHandle, "button-release-event", cast[pointer](pControlButtonReleaseSignal), cast[pointer](control)) 1037 | 1038 | gtk_widget_add_events(control.fHandle, GDK_POINTER_MOTION_MASK) 1039 | discard g_signal_connect_data(control.fHandle, "motion-notify-event", cast[pointer](pControlMotionNotifySignal), cast[pointer](control)) 1040 | 1041 | gtk_widget_add_events(control.fHandle, GDK_ENTER_NOTIFY_MASK) 1042 | discard g_signal_connect_data(control.fHandle, "enter-notify-event", cast[pointer](pControlEnterNotifySignal), cast[pointer](control)) 1043 | 1044 | gtk_widget_add_events(control.fHandle, GDK_LEAVE_NOTIFY_MASK) 1045 | discard g_signal_connect_data(control.fHandle, "leave-notify-event", cast[pointer](pControlLeaveNotifySignal), cast[pointer](control)) 1046 | 1047 | control.fIMContext = gtk_im_multicontext_new() 1048 | discard g_signal_connect_data(control.fIMContext, "commit", cast[pointer](pControlIMContextCommitSignal), cast[pointer](control)) 1049 | 1050 | procCall control.Control.init() 1051 | 1052 | method destroy(control: ControlImpl) = 1053 | procCall control.Control.destroy() 1054 | gtk_widget_destroy(control.fHandle) 1055 | # this destroys also child widgets 1056 | 1057 | method `visible=`(control: ControlImpl, visible: bool) = 1058 | procCall control.Control.`visible=`(visible) 1059 | if visible: 1060 | gtk_widget_show(control.fHandle) 1061 | else: 1062 | gtk_widget_hide(control.fHandle) 1063 | 1064 | # proc dummy(widget: pointer, event: var GdkEventButton, data: pointer): Gboolean {.cdecl.} = 1065 | # echo "dummy" 1066 | # result = true # Stop propagation 1067 | 1068 | method pUpdateScrollBar(control: ControlImpl) = 1069 | if control.fScrollableWidth == -1 and control.fScrollableHeight == -1: 1070 | return 1071 | echo "control.pUpdateScrollBar" 1072 | if control.fHScrollbar == nil: 1073 | if fScrollbarSize == -1: 1074 | return 1075 | # Init scrolling: 1076 | # echo "fScrollbarSize ", fScrollbarSize 1077 | control.fHAdjust = gtk_adjustment_new(0, 0, 0, 10, 10, 0) 1078 | control.fVAdjust = gtk_adjustment_new(0, 0, 0, 10, 10, 0) 1079 | control.fHScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, control.fHAdjust) 1080 | control.fVScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, control.fVAdjust) 1081 | gtk_container_add(control.fHandle, control.fHScrollbar) 1082 | gtk_container_add(control.fHandle, control.fVScrollbar) 1083 | discard g_signal_connect_data(control.fHAdjust, "value-changed", cast[pointer](pControlScollXSignal), cast[pointer](control)) 1084 | discard g_signal_connect_data(control.fVAdjust, "value-changed", cast[pointer](pControlScollYSignal), cast[pointer](control)) 1085 | 1086 | # The dead corner is an area which just needs to be covered with a control without function and the default background color 1087 | control.fDeadCornerHandle = gtk_label_new("") 1088 | # control.fDeadCornerHandle = gtk_fixed_new() 1089 | gtk_container_add(control.fHandle, control.fDeadCornerHandle) 1090 | var rgba: GdkRGBA 1091 | pColorToGdkRGBA(app.defaultBackgroundColor, rgba) 1092 | gtk_widget_override_background_color(control.fDeadCornerHandle, GTK_STATE_FLAG_NORMAL, rgba) 1093 | gtk_widget_set_size_request(control.fDeadCornerHandle, fScrollbarSize.cint, fScrollbarSize.cint) 1094 | 1095 | # Prevent that a click on the dead corner triggers the onClick event of the Control: 1096 | # gtk_widget_add_events(control.fDeadCornerHandle, GDK_BUTTON_PRESS_MASK) 1097 | # discard g_signal_connect_data(control.fDeadCornerHandle, "button-press-event", dummy, nil) 1098 | # gtk_widget_add_events(control.fDeadCornerHandle, GDK_BUTTON_RELEASE_MASK) 1099 | # discard g_signal_connect_data(control.fDeadCornerHandle, "button-release-event", dummy, nil) 1100 | # TODO: does not work. try EventBox 1101 | 1102 | # Calculation of scrollbar settings: 1103 | 1104 | control.fXScrollEnabled = false 1105 | control.fYScrollEnabled = false 1106 | 1107 | if control.scrollableWidth > control.width: 1108 | control.fXScrollEnabled = true 1109 | if control.scrollableHeight > control.height: 1110 | control.fYScrollEnabled = true 1111 | 1112 | if control.fXScrollEnabled and not control.fYScrollEnabled and control.scrollableHeight > control.height - fScrollbarSize: 1113 | control.fYScrollEnabled = true 1114 | if control.fYScrollEnabled and not control.fXScrollEnabled and control.scrollableWidth > control.width - fScrollbarSize: 1115 | control.fXScrollEnabled = true 1116 | 1117 | # Update horizontal scrollbar: 1118 | if control.fXScrollEnabled: 1119 | var upper = control.scrollableWidth 1120 | var size = control.width 1121 | if control.fYScrollEnabled: 1122 | upper.inc(fScrollbarSize) 1123 | size.dec(fScrollbarSize) 1124 | gtk_adjustment_set_upper(control.fHAdjust, upper.cdouble) 1125 | let value = gtk_adjustment_get_value(control.fHAdjust).int 1126 | let maxValue = upper - control.width 1127 | if value > maxValue: 1128 | gtk_adjustment_set_value(control.fHAdjust, maxValue.float) 1129 | gtk_adjustment_set_page_size(control.fHAdjust, control.width.float) 1130 | gtk_widget_set_size_request(control.fHScrollbar, size.cint, 0) 1131 | gtk_layout_move(control.fHandle, control.fHScrollbar, 0, (control.height - fScrollbarSize).cint) 1132 | gtk_widget_show(control.fHScrollbar) 1133 | # Ensure that scroll pos is within range: 1134 | control.fXScrollPos = max(min(control.fXScrollPos, maxValue), 0) 1135 | else: 1136 | gtk_widget_hide(control.fHScrollbar) 1137 | control.fXScrollPos = 0 1138 | 1139 | # Update vertical scrollbar: 1140 | if control.fYScrollEnabled: 1141 | var upper = control.scrollableHeight 1142 | var size = control.height 1143 | if control.fXScrollEnabled: 1144 | upper.inc(fScrollbarSize) 1145 | size.dec(fScrollbarSize) 1146 | gtk_adjustment_set_upper(control.fVAdjust, upper.cdouble) 1147 | let value = gtk_adjustment_get_value(control.fVAdjust).int 1148 | let maxValue = upper - control.height 1149 | if value > maxValue: 1150 | gtk_adjustment_set_value(control.fVAdjust, maxValue.float) 1151 | gtk_adjustment_set_page_size(control.fVAdjust, control.height.float) 1152 | gtk_widget_set_size_request(control.fVScrollbar, 0, size.cint) 1153 | gtk_layout_move(control.fHandle, control.fVScrollbar, (control.width - fScrollbarSize).cint, 0) 1154 | gtk_widget_show(control.fVScrollbar) 1155 | # Ensure that scroll pos is within range: 1156 | control.fYScrollPos = max(min(control.fYScrollPos, maxValue), 0) 1157 | else: 1158 | gtk_widget_hide(control.fVScrollbar) 1159 | control.fYScrollPos = 0 1160 | 1161 | # Update dead corner: 1162 | if control.fXScrollEnabled and control.fYScrollEnabled: 1163 | gtk_layout_move(control.fHandle, control.fDeadCornerHandle, (control.width - fScrollbarSize).cint, (control.height - fScrollbarSize).cint) 1164 | gtk_widget_show(control.fDeadCornerHandle) 1165 | else: 1166 | gtk_widget_hide(control.fDeadCornerHandle) 1167 | 1168 | 1169 | method setSize(control: ControlImpl, width, height: int) = 1170 | if width == control.fWidth and height == control.fHeight: 1171 | return 1172 | procCall control.Control.setSize(width, height) 1173 | gtk_widget_set_size_request(control.fHandle, width.cint, height.cint) 1174 | pUpdateScrollBar(control) 1175 | 1176 | method setPosition(control: ControlImpl, x, y: int) = 1177 | procCall control.Control.setPosition(x, y) 1178 | if control.fParentControl != nil: 1179 | gtk_layout_move(cast[ContainerImpl](control.fParentControl).fInnerHandle, control.fHandle, x.cint, y.cint) 1180 | 1181 | method forceRedraw(control: ControlImpl) = gtk_widget_queue_draw(control.fHandle) 1182 | 1183 | # proc removeWidgetInternal(container: WidgetContainer, widget: Widget) = gtk_container_remove(container.innerHandle, widget.handle) 1184 | 1185 | method setFontFamily(control: ControlImpl, fontFamily: string) = 1186 | procCall control.Control.setFontFamily(fontFamily) 1187 | control.pUpdateFont() 1188 | 1189 | method setFontSize(control: ControlImpl, fontSize: float) = 1190 | procCall control.Control.setFontSize(fontSize) 1191 | control.pUpdateFont() 1192 | 1193 | method setFontBold(control: ControlImpl, fontBold: bool) = 1194 | procCall control.Control.setFontBold(fontBold) 1195 | control.pUpdateFont() 1196 | 1197 | method setTextColor(control: ControlImpl, color: Color) = 1198 | procCall control.Control.setTextColor(color) 1199 | var rgba: GdkRGBA 1200 | color.pColorToGdkRGBA(rgba) 1201 | gtk_widget_override_color(control.fHandle, GTK_STATE_FLAG_NORMAL, rgba) 1202 | 1203 | method setBackgroundColor(control: ControlImpl, color: Color) = 1204 | procCall control.Control.setBackgroundColor(color) 1205 | var rgba: GdkRGBA 1206 | color.pColorToGdkRGBA(rgba) 1207 | gtk_widget_override_background_color(control.fHandle, GTK_STATE_FLAG_NORMAL, rgba) 1208 | 1209 | method getTextLineWidth(control: ControlImpl, text: string): int = 1210 | var layout = gtk_widget_create_pango_layout(control.fHandle, text) 1211 | var font = pCreateFont(control.fontFamily, control.fontSize, control.fontBold) 1212 | pango_layout_set_font_description(layout, font) 1213 | var width: cint 1214 | var height: cint 1215 | pango_layout_get_pixel_size(layout, width, height) 1216 | result = width + 2 1217 | g_object_unref(layout) 1218 | pango_font_description_free(font) 1219 | 1220 | method getTextLineHeight(control: ControlImpl): int = 1221 | var layout = gtk_widget_create_pango_layout(control.fHandle, "a") 1222 | var font = pCreateFont(control.fontFamily, control.fontSize, control.fontBold) 1223 | pango_layout_set_font_description(layout, font) 1224 | var width: cint 1225 | var height: cint 1226 | pango_layout_get_pixel_size(layout, width, height) 1227 | result = height 1228 | g_object_unref(layout) 1229 | pango_font_description_free(font) 1230 | 1231 | discard """ method `xScrollPos=`(control: ControlImpl, xScrollPos: int) = 1232 | procCall control.Control.`xScrollPos=`(xScrollPos) 1233 | control.pUpdateScrollBar() 1234 | 1235 | method `yScrollPos=`(control: ControlImpl, yScrollPos: int) = 1236 | procCall control.Control.`yScrollPos=`(yScrollPos) 1237 | control.pUpdateScrollBar() """ 1238 | 1239 | method `scrollableWidth=`(control: ControlImpl, scrollableWidth: int) = 1240 | if scrollableWidth == control.fScrollableWidth: 1241 | return 1242 | procCall control.Control.`scrollableWidth=`(scrollableWidth) 1243 | control.pUpdateScrollBar() 1244 | 1245 | method `scrollableHeight=`(control: ControlImpl, scrollableHeight: int) = 1246 | if scrollableHeight == control.fScrollableHeight: 1247 | return 1248 | procCall control.Control.`scrollableHeight=`(scrollableHeight) 1249 | control.pUpdateScrollBar() 1250 | 1251 | method mousePosition(control: Control): tuple[x, y: int] = 1252 | var x, y: cint 1253 | gtk_widget_get_pointer(cast[ControlImpl](control).fHandle, x, y) 1254 | result.x = x 1255 | result.y = y 1256 | 1257 | 1258 | # ---------------------------------------------------------------------------------------- 1259 | # Container 1260 | # ---------------------------------------------------------------------------------------- 1261 | 1262 | proc init(container: ContainerImpl) = 1263 | container.fHandle = gtk_fixed_new() 1264 | # ScrollWnd: 1265 | container.fScrollWndHandle = gtk_scrolled_window_new(nil, nil) 1266 | gtk_widget_show(container.fScrollWndHandle) 1267 | gtk_container_add(container.fHandle, container.fScrollWndHandle) 1268 | # Inner: 1269 | container.fInnerHandle = gtk_layout_new(nil, nil) 1270 | gtk_widget_show(container.fInnerHandle) 1271 | gtk_container_add(container.fScrollWndHandle, container.fInnerHandle) 1272 | container.Container.init() 1273 | 1274 | discard g_signal_connect_data(container.fInnerHandle, "draw", cast[pointer](pControlDrawSignal), cast[pointer](container)) 1275 | 1276 | method pAddButtonPressEvent(container: ContainerImpl) = 1277 | # Overwrite base method 1278 | gtk_widget_add_events(container.fInnerHandle, GDK_BUTTON_PRESS_MASK) 1279 | discard g_signal_connect_data(container.fInnerHandle, "button-press-event", cast[pointer](pCustomControlButtonPressSignal), cast[pointer](container)) 1280 | 1281 | method pUpdateScrollWnd(container: ContainerImpl) {.base.} = 1282 | let padding = container.getPadding() 1283 | let width = container.width - padding.left - padding.right 1284 | let height = container.height - padding.top - padding.bottom 1285 | gtk_widget_set_size_request(container.fScrollWndHandle, width.cint, height.cint) 1286 | gtk_fixed_move(container.fHandle, container.fScrollWndHandle, padding.left.cint, padding.top.cint) 1287 | 1288 | method `frame=`(container: ContainerImpl, frame: Frame) = 1289 | procCall container.Container.`frame=`(frame) 1290 | if frame != nil: 1291 | gtk_container_add(container.fHandle, frame.fHandle) 1292 | container.pUpdateScrollWnd() 1293 | 1294 | method add(container: ContainerImpl, control: Control) = 1295 | # Overwrite base method 1296 | gtk_container_add(container.fInnerHandle, cast[ControlImpl](control).fHandle) 1297 | procCall container.Container.add(control) 1298 | 1299 | method remove(container: ContainerImpl, control: Control) = 1300 | discard g_object_ref(cast[ControlImpl](control).fHandle) # avoid that the widget is destroyed 1301 | gtk_container_remove(container.fInnerHandle, cast[ControlImpl](control).fHandle) 1302 | procCall container.Container.remove(control) 1303 | 1304 | method paddingLeft(container: ContainerImpl): int {.base.} = 5 # TODO 1305 | method paddingRight(container: ContainerImpl): int {.base.} = 5 # TODO 1306 | method paddingTop(container: ContainerImpl): int {.base.} = 15 # TODO 1307 | method paddingBottom(container: ContainerImpl): int {.base.} = 5 # TODO 1308 | 1309 | method setInnerSize(container: ContainerImpl, width, height: int) = 1310 | procCall container.Container.setInnerSize(width, height) 1311 | gtk_widget_set_size_request(container.fInnerHandle, width.cint, height.cint) 1312 | 1313 | method setSize(container: ContainerImpl, width, height: int) = 1314 | procCall container.Container.setSize(width, height) 1315 | container.pUpdateScrollWnd() 1316 | 1317 | method pUpdateScrollBar(container: ContainerImpl) = 1318 | # Overwrite base method 1319 | if container.fScrollableWidth == -1 or container.fScrollableHeight == -1: 1320 | return 1321 | 1322 | if fScrollbarSize == -1: 1323 | return 1324 | 1325 | gtk_layout_set_size(container.fInnerHandle, container.scrollableWidth.cint, container.scrollableHeight.cint) 1326 | 1327 | container.fXScrollEnabled = false 1328 | container.fYScrollEnabled = false 1329 | 1330 | if container.scrollableWidth > container.width: 1331 | container.fXScrollEnabled = true 1332 | if container.scrollableHeight > container.height: 1333 | container.fYScrollEnabled = true 1334 | 1335 | if container.fXScrollEnabled and not container.fYScrollEnabled and container.scrollableHeight > container.height - fScrollbarSize: 1336 | container.fYScrollEnabled = true 1337 | if container.fYScrollEnabled and not container.fXScrollEnabled and container.scrollableWidth > container.width - fScrollbarSize: 1338 | container.fXScrollEnabled = true 1339 | 1340 | method handleDrawEvent(container: ContainerImpl, event: DrawEvent) = 1341 | # Overwrites base method 1342 | let callback = container.onDraw 1343 | if callback != nil: 1344 | callback(event) 1345 | else: 1346 | # Draw regular window background 1347 | container.canvas.areaColor = app.defaultBackgroundColor 1348 | container.canvas.fill() 1349 | 1350 | 1351 | # ---------------------------------------------------------------------------------------- 1352 | # Frame 1353 | # ---------------------------------------------------------------------------------------- 1354 | 1355 | proc init(frame: NativeFrame) = 1356 | frame.fHandle = gtk_frame_new("") 1357 | frame.Frame.init() 1358 | 1359 | method `text=`(frame: NativeFrame, text: string) = 1360 | procCall frame.Frame.`text=`(text) 1361 | gtk_frame_set_label(frame.fHandle, text) 1362 | 1363 | method getPadding(frame: NativeFrame): Spacing = 1364 | result = procCall frame.Frame.getPadding() 1365 | result.top = frame.getTextLineHeight() * frame.text.countLines + 2 1366 | 1367 | 1368 | # ---------------------------------------------------------------------------------------- 1369 | # Button 1370 | # ---------------------------------------------------------------------------------------- 1371 | 1372 | proc init(button: NativeButton) = 1373 | button.fHandle = gtk_button_new() 1374 | button.Button.init() 1375 | 1376 | method `text=`(button: NativeButton, text: string) = 1377 | procCall button.Button.`text=`(text) 1378 | gtk_button_set_label(button.fHandle, text) 1379 | # Don't let the button expand: 1380 | let list = gtk_container_get_children(button.fHandle) 1381 | if list != nil: 1382 | gtk_label_set_ellipsize(list.data, PANGO_ELLIPSIZE_END) 1383 | app.processEvents() 1384 | 1385 | method naturalWidth(button: NativeButton): int = 1386 | # Override parent method, to make it big enough for the text to fit in. 1387 | var context = gtk_widget_get_style_context(button.fHandle) 1388 | var padding: GtkBorder 1389 | gtk_style_context_get_padding(context, GTK_STATE_FLAG_NORMAL, padding) 1390 | result = button.getTextLineWidth(button.text) + padding.left + padding.right + 5 1391 | 1392 | method `enabled=`(button: NativeButton, enabled: bool) = 1393 | button.fEnabled = enabled 1394 | gtk_widget_set_sensitive(button.fHandle, enabled) 1395 | 1396 | 1397 | # ---------------------------------------------------------------------------------------- 1398 | # Checkbox 1399 | # ---------------------------------------------------------------------------------------- 1400 | 1401 | proc pControlToggledSignal(widget: pointer, data: pointer): Gboolean {.cdecl.} = 1402 | let control = cast[Checkbox](data) 1403 | var evt = new ToggleEvent 1404 | evt.control = control 1405 | try: 1406 | control.handleToggleEvent(evt) 1407 | except: 1408 | handleException() 1409 | 1410 | proc init(checkbox: NativeCheckbox) = 1411 | checkbox.fHandle = gtk_check_button_new() 1412 | discard g_signal_connect_data(checkbox.fHandle, "toggled", cast[pointer](pControlToggledSignal), cast[pointer](checkbox)) 1413 | checkbox.Checkbox.init() 1414 | gtk_widget_show(checkbox.fHandle) 1415 | 1416 | method `text=`(checkbox: NativeCheckbox, text: string) = 1417 | procCall checkbox.Checkbox.`text=`(text) 1418 | gtk_button_set_label(checkbox.fHandle, text) 1419 | app.processEvents() 1420 | 1421 | method naturalWidth(checkbox: NativeCheckbox): int = 1422 | # Override parent method, to make it big enough for the text to fit in. 1423 | var context = gtk_widget_get_style_context(checkbox.fHandle) 1424 | var padding: GtkBorder 1425 | gtk_style_context_get_padding(context, GTK_STATE_FLAG_NORMAL, padding) 1426 | result = checkbox.getTextLineWidth(checkbox.text) + padding.left + padding.right + 25.scaleToDpi 1427 | 1428 | method `enabled=`(checkbox: NativeCheckbox, enabled: bool) = 1429 | checkbox.fEnabled = enabled 1430 | gtk_widget_set_sensitive(checkbox.fHandle, enabled) 1431 | 1432 | method checked(checkbox: NativeCheckbox): bool = 1433 | result = gtk_toggle_button_get_active(checkbox.fHandle) 1434 | 1435 | method `checked=`(checkbox: NativeCheckbox, checked: bool) = 1436 | g_signal_handlers_block_matched(checkbox.fHandle, G_SIGNAL_MATCH_FUNC, 0, nil, nil, cast[pointer](pControlToggledSignal)) 1437 | gtk_toggle_button_set_active(checkbox.fHandle, checked) 1438 | g_signal_handlers_unblock_matched(checkbox.fHandle, G_SIGNAL_MATCH_FUNC, 0, nil, nil, cast[pointer](pControlToggledSignal)) 1439 | 1440 | method pAddButtonPressEvent(checkbox: NativeCheckbox) = discard # don't override default handler 1441 | 1442 | 1443 | # ---------------------------------------------------------------------------------------- 1444 | # ComboBox 1445 | # ---------------------------------------------------------------------------------------- 1446 | 1447 | proc pComboBoxChangedSignal(widget: pointer, data: pointer): Gboolean {.cdecl.} = 1448 | let control = cast[ComboBox](data) 1449 | var evt = new ComboBoxChangeEvent 1450 | evt.control = control 1451 | try: 1452 | control.handleChangeEvent(evt) 1453 | except: 1454 | handleException() 1455 | 1456 | proc init(comboBox: NativeComboBox) = 1457 | comboBox.fHandle = gtk_combo_box_text_new() 1458 | discard g_signal_connect_data(comboBox.fHandle, "changed", cast[pointer](pComboBoxChangedSignal), cast[pointer](comboBox)) 1459 | comboBox.ComboBox.init() 1460 | gtk_widget_show(comboBox.fHandle) 1461 | 1462 | method naturalWidth(comboBox: NativeComboBox): int = 1463 | result = 0 1464 | for option in comboBox.options: 1465 | result = max(result, comboBox.getTextWidth(option) + 20.scaleToDpi()) 1466 | 1467 | method naturalHeight(comboBox: NativeComboBox): int = 1468 | result = comboBox.getTextLineHeight() + 12.scaleToDpi() 1469 | 1470 | method `options=`(comboBox: NativeComboBox, options: seq[string]) = 1471 | let oldIndex = comboBox.index 1472 | comboBox.fOptions = options 1473 | 1474 | gtk_combo_box_text_remove_all(comboBox.fHandle) 1475 | for option in options: 1476 | gtk_combo_box_text_append_text(comboBox.fHandle, option.cstring) 1477 | 1478 | comboBox.triggerRelayout() 1479 | 1480 | if oldIndex < len(options): 1481 | comboBox.index = oldIndex 1482 | else: 1483 | comboBox.index = len(options) - 1 1484 | 1485 | method `enabled=`(comboBox: NativeComboBox, enabled: bool) = 1486 | comboBox.fEnabled = enabled 1487 | gtk_widget_set_sensitive(comboBox.fHandle, enabled) 1488 | 1489 | method value(comboBox: NativeComboBox): string = 1490 | result = $gtk_combo_box_text_get_active_text(comboBox.fHandle) 1491 | 1492 | method `value=`(comboBox: NativeComboBox, value: string) = 1493 | let idx = comboBox.fOptions.find(value) 1494 | if idx != -1: 1495 | comboBox.index = idx 1496 | 1497 | method index(comboBox: NativeComboBox): int = 1498 | result = gtk_combo_box_get_active(comboBox.fHandle) 1499 | 1500 | method `index=`(comboBox: NativeComboBox, index: int) = 1501 | gtk_combo_box_set_active(comboBox.fHandle, index.cint) 1502 | 1503 | 1504 | # ---------------------------------------------------------------------------------------- 1505 | # Label 1506 | # ---------------------------------------------------------------------------------------- 1507 | 1508 | proc init(label: NativeLabel) = 1509 | label.Label.init() 1510 | label.fFontSize = app.defaultFontSize * 0.95 1511 | 1512 | 1513 | # ---------------------------------------------------------------------------------------- 1514 | # ProgressBar 1515 | # ---------------------------------------------------------------------------------------- 1516 | 1517 | proc init(progressBar: NativeProgressBar) = 1518 | progressBar.fHandle = gtk_level_bar_new() 1519 | progressBar.ProgressBar.init() 1520 | progressBar.height = 8.scaleToDpi # adjust control height to bar height 1521 | 1522 | method `value=`(progressBar: NativeProgressBar, value: float) = 1523 | procCall progressBar.ProgressBar.`value=`(value) 1524 | gtk_level_bar_set_value(progressBar.fHandle, value) 1525 | app.processEvents() 1526 | 1527 | 1528 | # ---------------------------------------------------------------------------------------- 1529 | # TextBox 1530 | # ---------------------------------------------------------------------------------------- 1531 | 1532 | proc pTextBoxKeyPressSignal(widget: pointer, event: var GdkEventKey, data: pointer): Gboolean {.cdecl.} = 1533 | result = pControlKeyPressSignal(widget, event, data) 1534 | 1535 | # Implement own "copy to clipboard", because by default the clipboard is non-persistent 1536 | if not result: 1537 | let modifiers = gtk_accelerator_get_default_mod_mask() 1538 | if event.keyval == 'c'.ord and (event.state and modifiers) == GDK_CONTROL_MASK: 1539 | let textBox = cast[NativeTextBox](data) 1540 | app.clipboardText = textBox.selectedText 1541 | return true # prevent default "copy to clipboard" 1542 | 1543 | proc init(textBox: NativeTextBox) = 1544 | textBox.fHandle = gtk_entry_new() 1545 | discard g_signal_connect_data(textBox.fHandle, "changed", cast[pointer](pControlChangedSignal), cast[pointer](textBox)) 1546 | textBox.TextBox.init() 1547 | 1548 | method initStyle(textBox: NativeTextBox) = 1549 | procCall textBox.TextBox.initStyle() 1550 | var context = gtk_widget_get_style_context(textBox.fHandle) 1551 | var rgba: GdkRGBA 1552 | gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, rgba) 1553 | textBox.fBackgroundColor = rgba.pGdkRGBAToColor() 1554 | gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, rgba) 1555 | textBox.fTextColor = rgba.pGdkRGBAToColor() 1556 | textBox.fUseDefaultBackgroundColor = false 1557 | textBox.fUseDefaultTextColor = false 1558 | 1559 | method text(textBox: NativeTextBox): string = $gtk_entry_get_text(textBox.fHandle) 1560 | 1561 | method `text=`(textBox: NativeTextBox, text: string) = 1562 | gtk_entry_set_text(textBox.fHandle, text) 1563 | app.processEvents() 1564 | 1565 | method `placeholder=`(textBox: NativeTextBox, text: string) = 1566 | gtk_entry_set_placeholder_text(textBox.fHandle, text) 1567 | 1568 | method naturalHeight(textBox: NativeTextBox): int = textBox.getTextLineHeight() + 12 # add padding 1569 | 1570 | method setSize(textBox: NativeTextBox, width, height: int) = 1571 | gtk_entry_set_width_chars(textBox.fHandle, 1) 1572 | procCall textBox.ControlImpl.setSize(width, height) 1573 | 1574 | method pAddButtonPressEvent(textBox: NativeTextBox) = 1575 | gtk_widget_add_events(textBox.fHandle, GDK_BUTTON_PRESS_MASK) 1576 | discard g_signal_connect_data(textBox.fHandle, "button-press-event", cast[pointer](pDefaultControlButtonPressSignal), cast[pointer](textBox)) 1577 | 1578 | method pAddKeyPressEvent(textBox: NativeTextBox) = 1579 | discard g_signal_connect_data(textBox.fHandle, "key-press-event", cast[pointer](pTextBoxKeyPressSignal), cast[pointer](textBox)) 1580 | 1581 | method `editable=`(textBox: NativeTextBox, editable: bool) = 1582 | textBox.fEditable = editable 1583 | gtk_editable_set_editable(textBox.fHandle, editable) 1584 | 1585 | method cursorPos(textBox: NativeTextBox): int = 1586 | result = gtk_editable_get_position(textBox.fHandle) 1587 | 1588 | method `cursorPos=`(textBox: NativeTextBox, cursorPos: int) = 1589 | # side effect: clears selection 1590 | gtk_editable_set_position(textBox.fHandle, cursorPos.cint) 1591 | 1592 | method selectionStart(textBox: NativeTextBox): int = 1593 | var startPos: cint 1594 | var endPos: cint 1595 | discard gtk_editable_get_selection_bounds(textBox.fHandle, startPos, endPos) 1596 | result = startPos 1597 | 1598 | method selectionEnd(textBox: NativeTextBox): int = 1599 | var startPos: cint 1600 | var endPos: cint 1601 | discard gtk_editable_get_selection_bounds(textBox.fHandle, startPos, endPos) 1602 | result = endPos 1603 | 1604 | method `selectionStart=`(textBox: NativeTextBox, selectionStart: int) = 1605 | gtk_editable_select_region(textBox.fHandle, selectionStart.cint, textBox.selectionEnd.cint) 1606 | # side effect: sets cursor to end of selection 1607 | 1608 | method `selectionEnd=`(textBox: NativeTextBox, selectionEnd: int) = 1609 | gtk_editable_select_region(textBox.fHandle, textBox.selectionStart.cint, selectionEnd.cint) 1610 | # side effect: sets cursor to end of selection 1611 | 1612 | 1613 | # ---------------------------------------------------------------------------------------- 1614 | # TextArea 1615 | # ---------------------------------------------------------------------------------------- 1616 | 1617 | proc pTextAreaKeyPressSignal(widget: pointer, event: var GdkEventKey, data: pointer): Gboolean {.cdecl.} = 1618 | result = pControlKeyPressSignal(widget, event, data) 1619 | 1620 | # Implement own "copy to clipboard", because by default the clipboard is non-persistent 1621 | if not result: 1622 | let modifiers = gtk_accelerator_get_default_mod_mask() 1623 | if event.keyval == 'c'.ord and (event.state and modifiers) == GDK_CONTROL_MASK: 1624 | let textArea = cast[NativeTextBox](data) 1625 | app.clipboardText = textArea.selectedText 1626 | return true # prevent default "copy to clipboard" 1627 | 1628 | proc init(textArea: NativeTextArea) = 1629 | textArea.fHandle = gtk_scrolled_window_new(nil, nil) 1630 | # gtk_scrolled_window_set_policy(textArea.fHandle, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC) 1631 | gtk_scrolled_window_set_policy(textArea.fHandle, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC) 1632 | textArea.fTextViewHandle = gtk_text_view_new() 1633 | gtk_text_view_set_left_margin(textArea.fTextViewHandle, 5) 1634 | gtk_text_view_set_right_margin(textArea.fTextViewHandle, 5) 1635 | gtk_text_view_set_top_margin(textArea.fTextViewHandle, 5) 1636 | gtk_text_view_set_bottom_margin(textArea.fTextViewHandle, 5) 1637 | gtk_container_add(textArea.fHandle, textArea.fTextViewHandle) 1638 | gtk_widget_show(textArea.fTextViewHandle) 1639 | textArea.fBufferHandle = gtk_text_view_get_buffer(textArea.fTextViewHandle) 1640 | discard g_signal_connect_data(textArea.fBufferHandle, "changed", cast[pointer](pControlChangedSignal), cast[pointer](textArea)) 1641 | textArea.TextArea.init() 1642 | 1643 | method setSize(textBox: NativeTextArea, width, height: int) = 1644 | # Need to override method of NativeTextBox 1645 | procCall textBox.ControlImpl.setSize(width, height) 1646 | 1647 | method text(textArea: NativeTextArea): string = 1648 | var startIter, endIter: GtkTextIter 1649 | gtk_text_buffer_get_start_iter(textArea.fBufferHandle, startIter) 1650 | gtk_text_buffer_get_end_iter(textArea.fBufferHandle, endIter) 1651 | result = $gtk_text_buffer_get_text(textArea.fBufferHandle, startIter, endIter, true) 1652 | 1653 | method `text=`(textArea: NativeTextArea, text: string) = 1654 | gtk_text_buffer_set_text(textArea.fBufferHandle, text, text.len.cint) 1655 | app.processEvents() 1656 | 1657 | method addText(textArea: NativeTextArea, text: string) = 1658 | # overide base method for better performance and to prevent automatic scrolling to top 1659 | var iter: GtkTextIter 1660 | gtk_text_buffer_get_end_iter(textArea.fBufferHandle, iter) 1661 | gtk_text_buffer_insert(textArea.fBufferHandle, iter, text, text.len.cint) 1662 | app.processEvents() 1663 | 1664 | method addColoredText(textArea: NativeTextArea, text, color: string) = 1665 | if color == "": 1666 | textArea.addText(text) 1667 | else: 1668 | if not textArea.fColorTags.contains(color): 1669 | discard gtk_text_buffer_create_tag(textArea.fBufferHandle, color, "foreground", color, nil) 1670 | textArea.fColorTags.add(color) 1671 | var iter: GtkTextIter 1672 | gtk_text_buffer_get_end_iter(textArea.fBufferHandle, iter) 1673 | gtk_text_buffer_insert_with_tags_by_name(textArea.fBufferHandle, iter, text, text.len.cint, color, nil) 1674 | app.processEvents() 1675 | 1676 | method scrollToBottom(textArea: NativeTextArea) = 1677 | var iter: GtkTextIter 1678 | gtk_text_buffer_get_end_iter(textArea.fBufferHandle, iter) 1679 | gtk_text_view_scroll_to_iter(textArea.fTextViewHandle, iter, 0, false, 0, 0) 1680 | app.processEvents() 1681 | 1682 | method `wrap=`(textArea: NativeTextArea, wrap: bool) = 1683 | procCall textArea.TextArea.`wrap=`(wrap) 1684 | if wrap: 1685 | gtk_text_view_set_wrap_mode(textArea.fTextViewHandle, GTK_WRAP_WORD_CHAR) 1686 | else: 1687 | gtk_text_view_set_wrap_mode(textArea.fTextViewHandle, GTK_WRAP_NONE) 1688 | 1689 | method pAddKeyPressEvent(textArea: NativeTextArea) = 1690 | discard g_signal_connect_data(textArea.fTextViewHandle, "key-press-event", cast[pointer](pTextAreaKeyPressSignal), cast[pointer](textArea)) 1691 | 1692 | method `editable=`(textArea: NativeTextArea, editable: bool) = 1693 | textArea.fEditable = editable 1694 | gtk_text_view_set_editable(textArea.fTextViewHandle, editable) 1695 | 1696 | method cursorPos(textArea: NativeTextArea): int = 1697 | let mark = gtk_text_buffer_get_insert(textArea.fBufferHandle) 1698 | var iter: GtkTextIter 1699 | gtk_text_buffer_get_iter_at_mark(textArea.fBufferHandle, iter, mark) 1700 | result = gtk_text_iter_get_offset(iter) 1701 | 1702 | method `cursorPos=`(textArea: NativeTextArea, cursorPos: int) = 1703 | # side effect: clears selection 1704 | var iter: GtkTextIter 1705 | gtk_text_buffer_get_iter_at_offset(textArea.fBufferHandle, iter, cursorPos.cint) 1706 | gtk_text_buffer_select_range(textArea.fBufferHandle, iter, iter) 1707 | 1708 | method selectionStart(textArea: NativeTextArea): int = 1709 | var startIter: GtkTextIter 1710 | var endIter: GtkTextIter 1711 | discard gtk_text_buffer_get_selection_bounds(textArea.fBufferHandle, startIter, endIter) 1712 | result = gtk_text_iter_get_offset(startIter) 1713 | 1714 | method selectionEnd(textArea: NativeTextArea): int = 1715 | var startIter: GtkTextIter 1716 | var endIter: GtkTextIter 1717 | discard gtk_text_buffer_get_selection_bounds(textArea.fBufferHandle, startIter, endIter) 1718 | result = gtk_text_iter_get_offset(endIter) 1719 | 1720 | method `selectionStart=`(textArea: NativeTextArea, selectionStart: int) = 1721 | var startIter: GtkTextIter 1722 | var endIter: GtkTextIter 1723 | gtk_text_buffer_get_iter_at_offset(textArea.fBufferHandle, startIter, selectionStart.cint) 1724 | gtk_text_buffer_get_iter_at_offset(textArea.fBufferHandle, endIter, textArea.selectionEnd.cint) 1725 | gtk_text_buffer_select_range(textArea.fBufferHandle, startIter, endIter) 1726 | # side effect: sets cursor to start of selection 1727 | 1728 | method `selectionEnd=`(textArea: NativeTextArea, selectionEnd: int) = 1729 | var startIter: GtkTextIter 1730 | var endIter: GtkTextIter 1731 | gtk_text_buffer_get_iter_at_offset(textArea.fBufferHandle, startIter, textArea.selectionStart.cint) 1732 | gtk_text_buffer_get_iter_at_offset(textArea.fBufferHandle, endIter, selectionEnd.cint) 1733 | gtk_text_buffer_select_range(textArea.fBufferHandle, startIter, endIter) 1734 | # side effect: sets cursor to start of selection 1735 | -------------------------------------------------------------------------------- /src/nigui/private/gtk3/platform_types1.nim: -------------------------------------------------------------------------------- 1 | # NiGui - GTK+ 3 platform-specific code - part 1 2 | 3 | # This file will be included in "nigui.nim". 4 | 5 | type 6 | WindowImpl* = ref object of Window 7 | fHandle: pointer 8 | fInnerHandle: pointer 9 | fIMContext: pointer 10 | fKeyPressed: Key 11 | 12 | ControlImpl* = ref object of Control 13 | fHandle: pointer 14 | fHScrollbar: pointer 15 | fVScrollbar: pointer 16 | fHAdjust: pointer 17 | fVAdjust: pointer 18 | fDeadCornerHandle: pointer 19 | fIMContext: pointer 20 | fKeyPressed: Key 21 | 22 | CanvasImpl* = ref object of Canvas 23 | fSurface: pointer 24 | fData: ptr UncheckedArray[byte] 25 | fStride: int 26 | fCairoContext: pointer 27 | fFont: pointer 28 | 29 | ImageImpl* = ref object of Image 30 | -------------------------------------------------------------------------------- /src/nigui/private/gtk3/platform_types2.nim: -------------------------------------------------------------------------------- 1 | # NiGui - GTK+ 3 platform-specific code - part 2 2 | 3 | # This file will be included in "nigui.nim". 4 | 5 | type 6 | ContainerImpl* = ref object of Container 7 | fScrollWndHandle: pointer 8 | fInnerHandle: pointer 9 | 10 | NativeFrame* = ref object of Frame 11 | 12 | NativeButton* = ref object of Button 13 | 14 | NativeCheckbox* = ref object of Checkbox 15 | 16 | NativeComboBox* = ref object of ComboBox 17 | 18 | NativeLabel* = ref object of Label 19 | 20 | NativeProgressBar* = ref object of ProgressBar 21 | 22 | NativeTextBox* = ref object of TextBox 23 | -------------------------------------------------------------------------------- /src/nigui/private/gtk3/platform_types3.nim: -------------------------------------------------------------------------------- 1 | # NiGui - GTK+ 3 platform-specific code - part 3 2 | 3 | # This file will be included in "nigui.nim". 4 | 5 | type 6 | NativeTextArea* = ref object of TextArea 7 | fTextViewHandle: pointer 8 | fBufferHandle: pointer 9 | fColorTags: seq[string] 10 | -------------------------------------------------------------------------------- /src/nigui/private/windows/platform_types1.nim: -------------------------------------------------------------------------------- 1 | import windows 2 | 3 | # NiGui - Win32 platform-specific code - part 1 4 | 5 | # This file will be included in "nigui.nim". 6 | 7 | type 8 | WindowImpl* = ref object of Window 9 | fHandle: pointer 10 | fModalParent: WindowImpl 11 | fFocusedControl: ControlImpl 12 | 13 | ControlImpl* = ref object of Control 14 | fHandle: pointer 15 | fFont: pointer 16 | fBackgroundBrush: pointer 17 | 18 | CanvasImpl* = ref object of Canvas 19 | fDC: pointer 20 | fBitmap: pointer 21 | fGraphics: pointer 22 | fFont: pointer 23 | fFontBrush: pointer 24 | fLinePen: pointer 25 | fAreaBrush: pointer 26 | 27 | ImageImpl* = ref object of Image 28 | bitmapDataLockBits: BitmapData 29 | -------------------------------------------------------------------------------- /src/nigui/private/windows/platform_types2.nim: -------------------------------------------------------------------------------- 1 | # NiGui - Win32 platform-specific code - part 2 2 | 3 | # This file will be included in "nigui.nim". 4 | 5 | type 6 | ContainerImpl* = ref object of Container 7 | fScrollWndHandle: pointer 8 | fInnerHandle: pointer 9 | 10 | NativeFrame* = ref object of Frame 11 | 12 | NativeButton* = ref object of Button 13 | 14 | NativeCheckbox* = ref object of Checkbox 15 | 16 | NativeComboBox* = ref object of ComboBox 17 | 18 | NativeLabel* = ref object of Label 19 | 20 | NativeProgressBar* = ref object of ProgressBar 21 | 22 | NativeTextBox* = ref object of TextBox 23 | -------------------------------------------------------------------------------- /src/nigui/private/windows/platform_types3.nim: -------------------------------------------------------------------------------- 1 | # NiGui - Win32 platform-specific code - part 3 2 | 3 | # This file will be included in "nigui.nim". 4 | 5 | type 6 | NativeTextArea* = ref object of TextArea 7 | -------------------------------------------------------------------------------- /src/nigui/private/windows/res/amd64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonkrauter/NiGui/f8167a82517973d7cd47797801f90b06360e3f32/src/nigui/private/windows/res/amd64.o -------------------------------------------------------------------------------- /src/nigui/private/windows/res/i386.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonkrauter/NiGui/f8167a82517973d7cd47797801f90b06360e3f32/src/nigui/private/windows/res/i386.o -------------------------------------------------------------------------------- /src/nigui/private/windows/res/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 62 | 63 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/nigui/private/windows/res/resource.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) 2 | 3 | 1 MANIFEST "manifest.xml" 4 | -------------------------------------------------------------------------------- /src/nigui/private/windows/windows.nim: -------------------------------------------------------------------------------- 1 | # NiGui - minimal Win32 binding 2 | 3 | # Some functions requires Windows XP or newer. 4 | # Windows type names are replaced with basic types. 5 | 6 | # Type aliases for int16: 7 | # ATOM, SHORT, USHORT, LANGID 8 | 9 | # Type aliases for int32: 10 | # int, UINT, WINUINT, DWORD, LONG, COLORREF 11 | 12 | # Type aliases for int: 13 | # WPARAM, LPARAM, ULONG 14 | 15 | # Type aliases for pointer: 16 | # WNDPROC, HINSTANCE, HICON, HCURSOR, HBRUSH, HWND, LPMSG, LRESULT, PACTCTX, HMODULE, HDC, HGDIOBJ, HFONT, HMONITOR, HGDIOBJ 17 | 18 | # Type aliases for cstring: 19 | # LPCTSTR, LPCWSTR 20 | 21 | {.pragma: libUser32, stdcall, dynlib: "User32.dll".} 22 | {.pragma: libKernel32, stdcall, dynlib: "Kernel32.dll".} 23 | {.pragma: libGdi32, stdcall, dynlib: "Gdi32.dll".} 24 | {.pragma: libShell32, stdcall, dynlib: "Shell32.dll".} 25 | {.pragma: libGdiplus, stdcall, dynlib: "Gdiplus.dll".} 26 | {.pragma: libComdlg32, stdcall, dynlib: "Comdlg32.dll".} 27 | 28 | 29 | # ---------------------------------------------------------------------------------------- 30 | # Constants 31 | # ---------------------------------------------------------------------------------------- 32 | 33 | const 34 | # ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID* = 4 35 | # ACTCTX_FLAG_RESOURCE_NAME_VALID* = 8 36 | # ACTCTX_FLAG_SET_PROCESS_DEFAULT* = 16 37 | BIF_RETURNONLYFSDIRS* = 0x00000001 38 | BIF_NEWDIALOGSTYLE* = 0x00000040 39 | BN_CLICKED* = 0 40 | BM_SETSTYLE* = 244 41 | BM_SETIMAGE* = 247 42 | BM_GETCHECK* = 240 43 | BM_SETCHECK* = 241 44 | BM_GETSTATE* = 242 45 | BS_DEFPUSHBUTTON* = 0x00000001 46 | BS_AUTOCHECKBOX* = 0x00000003 47 | BS_GROUPBOX* = 0x00000007 48 | BST_UNCHECKED* = 0 49 | BST_CHECKED* = 1 50 | CB_ERR* = -1 51 | CB_ADDSTRING* = 0x0143 52 | CB_DELETESTRING* = 0x0144 53 | CB_GETCOUNT* = 0x0146 54 | CB_GETCURSEL* = 0x0147 55 | CB_SELECTSTRING* = 0x014D 56 | CB_SETCURSEL* = 0x014E 57 | CBS_SIMPLE* = 1 58 | CBS_DROPDOWN* = 2 59 | CBS_DROPDOWNLIST* = 3 60 | CBN_SELCHANGE* = 1 61 | CF_TEXT* = 1 62 | COLOR_BTNFACE* = 15 63 | COLOR_WINDOW* = 5 64 | COLOR_WINDOWTEXT* = 8 65 | CP_UTF8* = 65001 66 | CS_HREDRAW* = 2 67 | CS_VREDRAW* = 1 68 | DEFAULT_GUI_FONT* = 17 69 | EM_SCROLLCARET* = 183 70 | EM_GETSEL* = 176 71 | EM_SETSEL* = 177 72 | EM_REPLACESEL* = 194 73 | EM_SETREADONLY* = 207 74 | EN_CHANGE* = 768 75 | ES_MULTILINE* = 4 76 | ES_AUTOHSCROLL* = 0x80 77 | EM_SETCUEBANNER* = 0x1501 78 | GCLP_HBRBACKGROUND* = -10 79 | GWL_EXSTYLE* = -20 80 | GWL_HINSTANCE* = -6 81 | GWL_HWNDPARENT* = -8 82 | GWL_STYLE* = -16 83 | GWLP_USERDATA* = -21 84 | GWLP_WNDPROC* = -4 85 | HWND_TOPMOST* = -1 86 | HWND_NOTOPMOST* = -2 87 | ICON_SMALL* = 0 88 | ICON_BIG* = 1 89 | IDC_ARROW* = 32512 90 | INVALID_HANDLE_VALUE* = cast[pointer](-1) 91 | InterpolationMode_Default* = 0 92 | InterpolationMode_Bilinear* = 3 93 | InterpolationMode_NearestNeighbor* = 5 94 | IMAGE_BITMAP* = 0 95 | IMAGE_ICON* = 1 96 | # LR_LOADFROMFILE* = 16 97 | OBJ_BRUSH* = 2 98 | PBM_SETPOS* = 1026 99 | PBM_SETRANGE32* = 1030 100 | PM_REMOVE* = 1 101 | SB_HORZ* = 0 102 | SB_THUMBPOSITION* = 4 103 | SB_THUMBTRACK* = 5 104 | SB_VERT* = 1 105 | SB_LINELEFT* = 0 106 | SB_LINERIGHT* = 1 107 | SB_PAGELEFT* = 2 108 | SB_PAGERIGHT* = 3 109 | SC_MINIMIZE* = 0xF020 110 | SC_RESTORE* = 0xF120 111 | SIF_ALL* = 23 112 | SM_CXVSCROLL* = 2 113 | SPI_GETWORKAREA* = 0x0030 114 | SPI_SETKEYBOARDCUES* = 0x100B 115 | # SS_CENTERIMAGE* = 0x00000200 116 | SW_HIDE* = 0 117 | # SW_MAXIMIZE = 3 118 | SW_SHOW* = 5 119 | SW_MINIMIZE* = 6 120 | SW_RESTORE* = 9 121 | SWP_NOMOVE* = 2 122 | SWP_NOSIZE* = 1 123 | VK_RETURN* = 13 124 | VK_SHIFT* = 16 125 | VK_CONTROL* = 17 126 | VK_MENU* = 18 127 | VK_PRIOR* = 33 128 | VK_NEXT* = 34 129 | VK_END* = 35 130 | VK_HOME* = 36 131 | VK_LEFT* = 37 132 | VK_UP* = 38 133 | VK_RIGHT* = 39 134 | VK_DOWN* = 40 135 | VK_SNAPSHOT* = 44 136 | VK_INSERT* = 45 137 | VK_DELETE* = 46 138 | VK_LSHIFT* = 160 139 | VK_RSHIFT* = 161 140 | VK_LCONTROL* = 162 141 | VK_RCONTROL* = 163 142 | VK_LMENU* = 164 143 | VK_RMENU* = 165 144 | VK_OEM_PLUS* = 187 145 | VK_OEM_COMMA* = 188 146 | VK_OEM_MINUS* = 189 147 | VK_OEM_PERIOD* = 190 148 | VK_OEM_2* = 191 149 | VK_OEM_4* = 219 150 | VK_OEM_5* = 220 151 | VK_OEM_102* = 226 152 | WM_USER* = 0x0400 153 | WM_APP* = 0x8000 154 | WM_ACTIVATE* = 0x0006 155 | WM_CHANGEUISTATE* = 0x0127 156 | WM_CHAR* = 258 157 | WM_CLOSE* = 16 158 | WM_COMMAND* = 273 159 | WM_DROPFILES* = 563 160 | WM_ERASEBKGND* = 20 161 | WM_HSCROLL* = 276 162 | WM_KEYDOWN* = 256 163 | WM_KEYUP* = 257 164 | WM_MOUSEMOVE* = 0x0200 165 | WM_LBUTTONDOWN* = 0x0201 166 | WM_LBUTTONUP* = 0x0202 167 | WM_MBUTTONDOWN* = 0x0207 168 | WM_MBUTTONUP* = 0x0208 169 | # WM_NCLBUTTONDOWN* = 161 170 | # WM_NCLBUTTONUP* = 162 171 | WM_MOUSEWHEEL* = 0x020A 172 | WM_MOUSELEAVE* = 0x02A3 173 | WM_MOVE* = 3 174 | WM_NEXTDLGCTL* = 0x0028 175 | WM_PAINT* = 15 176 | WM_RBUTTONDOWN* = 0x0204 177 | WM_RBUTTONUP* = 0x0205 178 | WM_SETFOCUS* = 0x0007 179 | WM_KILLFOCUS* = 0x0008 180 | WM_SETFONT* = 48 181 | WM_SIZE* = 5 182 | WM_VSCROLL* = 277 183 | WM_SETICON* = 128 184 | WM_SYSKEYDOWN* = 260 185 | WM_SYSKEYUP* = 261 186 | WM_SYSCOMMAND* = 274 187 | WM_CTLCOLOREDIT* = 307 188 | WM_CTLCOLORSTATIC* = 312 189 | WM_CUT* = 0x0300 190 | WM_COPY* = 0x0301 191 | WM_PASTE* = 0x0302 192 | WM_GETMINMAXINFO* = 0x0024 193 | WS_CLIPCHILDREN* = 0x02000000 194 | WS_CAPTION* = 0x00C00000 195 | WS_CHILD* = 0x40000000 196 | WS_EX_CLIENTEDGE* = 0x00000200 197 | WS_GROUP* = 0x00020000 198 | WS_HSCROLL* = 0x00100000 199 | WS_OVERLAPPEDWINDOW* = 0x00CF0000 200 | WS_SYSMENU* = 0x00080000 201 | WS_MINIMIZEBOX* = 0x00020000 202 | WS_MAXIMIZEBOX* = 0x00010000 203 | WS_TABSTOP* = 0x00010000 204 | WS_THICKFRAME* = 0x00040000 205 | WS_VSCROLL* = 0x00200000 206 | WS_EX_CONTROLPARENT* = 0x00010000 207 | WS_VISIBLE* = 0x10000000 208 | # DT_CALCRECT* = 1024 209 | # OBJ_FONT* = 6 210 | # SM_XVIRTUALSCREEN* = 76 211 | # WC_LISTVIEWW* = "SysListView32" 212 | # WC_TABCONTROLW* = "SysTabControl32" 213 | # WC_TREEVIEWW* = "SysTreeView32" 214 | # WM_CTLCOLORSTATIC* = 312 215 | # WS_EX_TOOLWINDOW* = 0x00000080 216 | OPAQUE* = 2 217 | TRANSPARENT* = 1 218 | PS_SOLID* = 0 219 | PS_DASH* = 1 220 | PS_DOT* = 2 221 | PS_DASHDOT* = 3 222 | PS_DASHDOTDOT* = 4 223 | PS_NULL* = 5 224 | PS_USERSTYLE* = 7 225 | PS_INSIDEFRAME* = 6 226 | OFN_ALLOWMULTISELECT* = 0x00000200 227 | OFN_EXPLORER* = 0x00080000 228 | OFN_FILEMUSTEXIST* = 0x00001000 229 | OFN_OVERWRITEPROMPT* = 0x00000002 230 | # UnitWorld* = 0 231 | # UnitDisplay* = 1 232 | UnitPixel* = 2 233 | # UnitPoint* = 3 234 | # UnitInch* = 4 235 | # UnitDocument* = 5 236 | # UnitMillimeter* = 6 237 | GMEM_MOVEABLE* = 2 238 | PixelFormat32bppARGB* = 2498570 239 | ImageLockModeWrite* = 2 240 | MAPVK_VSC_TO_VK_EX* = 3 241 | PROCESS_DPI_UNAWARE* = 0 242 | PROCESS_SYSTEM_DPI_AWARE* = 1 243 | PROCESS_PER_MONITOR_DPI_AWARE* = 2 244 | FontStyleRegular* = 0 245 | FontStyleBold* = 1 246 | TextRenderingHint_AntiAlias* = 4 247 | TME_LEAVE* = 0x00000002 248 | 249 | 250 | # ---------------------------------------------------------------------------------------- 251 | # Types 252 | # ---------------------------------------------------------------------------------------- 253 | 254 | type 255 | WndClassEx* = object 256 | cbSize*: int32 257 | style*: int32 258 | lpfnWndProc*: pointer 259 | cbClsExtra*: int32 260 | cbWndExtra*: int32 261 | hInstance*: pointer 262 | hIcon*: pointer 263 | hCursor*: pointer 264 | hbrBackground*: pointer 265 | lpszMenuName*: cstring 266 | lpszClassName*: cstring 267 | hIconSm*: pointer 268 | 269 | Point* = object 270 | x*: int32 271 | y*: int32 272 | 273 | Size* = object 274 | cx*: int32 275 | cy*: int32 276 | 277 | Rect* = object 278 | left*: int32 279 | top*: int32 280 | right*: int32 281 | bottom*: int32 282 | 283 | RectF* = object 284 | x*: cfloat 285 | y*: cfloat 286 | width*: cfloat 287 | height*: cfloat 288 | 289 | RGB32* = object 290 | red*: byte 291 | green*: byte 292 | blue*: byte 293 | unused: byte 294 | 295 | ARGB* = object 296 | blue*: byte 297 | green*: byte 298 | red*: byte 299 | alpha*: byte 300 | 301 | Msg* = object 302 | hwnd*: pointer 303 | message*: int32 304 | wParam*: int 305 | lParam*: int 306 | time*: int32 307 | pt*: Point 308 | 309 | ActCtx* = object 310 | cbSize*: int32 311 | dwFlags*: int32 312 | lpSource*: cstring 313 | wProcessorArchitecture*: int16 314 | wLangId*: int16 315 | lpAssemblyDirectory*: cstring 316 | lpResourceName*: cstring 317 | lpApplicationName*: cstring 318 | hModule*: pointer 319 | 320 | ScrollInfo* = object 321 | cbSize*: int32 322 | fMask*: int32 323 | nMin*: int32 324 | nMax*: int32 325 | nPage*: int32 326 | nPos*: int32 327 | nTrackPos*: int32 328 | 329 | MonitorInfo * = object 330 | cbSize*: int32 331 | rcMonitor*: Rect 332 | rcWork*: Rect 333 | dwFlags*: int32 334 | 335 | PaintStruct* = array[68, byte] 336 | 337 | KeyState* = array[256, byte] 338 | 339 | GdiplusStartupInput* = object 340 | GdiplusVersion*: int32 341 | DebugEventCallback*: pointer 342 | SuppressBackgroundThread*: bool 343 | SuppressExternalCodecs*: bool 344 | 345 | OpenFileName* = object 346 | lStructSize*: int32 347 | hwndOwner*: pointer 348 | hInstance*: pointer 349 | lpstrFilter*: cstring 350 | lpstrCustomFilter*: cstring 351 | nMaxCustFilter*: int32 352 | nFilterIndex*: int32 353 | lpstrFile*: cstring 354 | nMaxFile*: int32 355 | lpstrFileTitle*: cstring 356 | nMaxFileTitle*: int32 357 | lpstrInitialDir*: cstring 358 | lpstrTitle*: cstring 359 | Flags*: int32 360 | nFileOffset*: int16 361 | nFileExtension*: int16 362 | lpstrDefExt*: cstring 363 | lCustData*: pointer 364 | lpfnHook*: pointer 365 | lpTemplateName*: cstring 366 | # pvReserved: pointer 367 | # dwReserved: int32 368 | # FlagsEx*: int32 369 | 370 | GUID* = object 371 | Data1*: int32 372 | Data2*: int32 373 | Data3*: int32 374 | Data4*: int32 375 | 376 | BitmapData* = object 377 | Width*: int32 378 | Height*: int32 379 | Stride*: int32 380 | PixelFormat*: int32 381 | Scan0*: ptr UncheckedArray[byte] 382 | Reserved: pointer 383 | 384 | BrowseInfo* = object 385 | hwndOwner*: pointer 386 | pidlRoot*: pointer 387 | pszDisplayName*: cstring 388 | lpszTitle*: cstring 389 | ulFlags*: uint 390 | lpfn*: pointer 391 | lParam*: pointer 392 | iImage*: int32 393 | 394 | MinMaxInfo* = object 395 | ptReserved*: Point 396 | ptMaxSize*: Point 397 | ptMaxPosition*: Point 398 | ptMinTrackSize*: Point 399 | ptMaxTrackSize*: Point 400 | 401 | TTrackMouseEvent* {.pure.} = object 402 | cbSize*: int32 403 | dwFlags*: int32 404 | hwndTrack*: pointer 405 | dwHoverTime*: int32 406 | 407 | 408 | # ---------------------------------------------------------------------------------------- 409 | # Replacement for Windows Macros 410 | # ---------------------------------------------------------------------------------------- 411 | 412 | import math 413 | 414 | proc loWord*(param: pointer): int = cast[int](param) and 0x0000FFFF 415 | 416 | proc hiWord*(param: pointer): int = 417 | result = (cast[int](param) shr 16) and 0xFFFF 418 | if result > 2^15: 419 | result = result - 2^16 420 | 421 | 422 | # ---------------------------------------------------------------------------------------- 423 | # Kernel32 Procs 424 | # ---------------------------------------------------------------------------------------- 425 | 426 | proc LoadLibraryA*(lpFileName: cstring): pointer {.importc, libKernel32.} 427 | # proc GetModuleHandleA*(lpModuleName: cstring): pointer {.importc, libKernel32.} 428 | proc GetLastError*(): int {.importc, libKernel32.} 429 | # proc CreateActCtxA*(pActCtx: var ActCtx): pointer {.importc, libKernel32.} 430 | # proc GetSystemDirectoryA*(lpBuffer: pointer, uSize: int32): int32 {.importc, libKernel32.} 431 | proc MultiByteToWideChar*(CodePage, dwFlags: int32, lpMultiByteStr: cstring, cbMultiByte: int32, lpWideCharStr: cstring, cchWideChar: int32): int32 {.importc, libKernel32.} 432 | proc WideCharToMultiByte*(CodePage, dwFlags: int32, lpWideCharStr: cstring, cchWideChar: int32, lpMultiByteStr: cstring, cbMultiByte: int32, lpDefaultChar: cstring, lpUsedDefaultChar: pointer): int32 {.importc, libKernel32.} 433 | proc GlobalLock*(hMem: pointer): pointer {.importc, libKernel32.} 434 | proc GlobalUnlock*(hMem: pointer): bool {.importc, libKernel32.} 435 | proc GlobalAlloc*(uFlags, dwBytes: int32): pointer {.importc, libKernel32.} 436 | 437 | 438 | # ---------------------------------------------------------------------------------------- 439 | # User32 Procs 440 | # ---------------------------------------------------------------------------------------- 441 | 442 | proc MessageBoxW*(hWnd: pointer, lpText, lpCaption: cstring, uType: int) {.importc, libUser32.} 443 | proc RegisterClassExW*(lpwcx: var WndClassEx): int16 {.importc, libUser32.} 444 | proc CreateWindowExW*(dwExStyle: int32, lpClassName, lpWindowName: cstring, dwStyle: int32, x, y, nWidth, nHeight: int, hWndParent, hMenu, hInstance, lpParam: pointer): pointer {.importc, libUser32.} 445 | proc DestroyWindow*(hWnd: pointer): bool {.importc, libUser32.} 446 | proc ShowWindow*(hWnd: pointer, nCmdShow: int32): bool {.importc, libUser32.} 447 | proc EnableWindow*(hWnd: pointer, bEnable: bool): bool {.importc, libUser32.} 448 | proc DefWindowProcW*(hWnd: pointer, uMsg: int, wParam, lParam: pointer): pointer {.importc, libUser32.} 449 | proc GetMessageW*(lpMsg, hWnd: pointer, wMsgFilterMin, wMsgFilterMax: int32): bool {.importc, libUser32.} 450 | proc PeekMessageW*(lpMsg, hWnd: pointer, wMsgFilterMin, wMsgFilterMax, wRemoveMsg: int32): bool {.importc, libUser32.} 451 | proc TranslateMessage*(lpMsg: pointer): bool {.importc, libUser32.} 452 | proc DispatchMessageW*(lpMsg: pointer): pointer {.importc, libUser32.} 453 | proc SetParent*(hWndChild, hWndNewParent: pointer): pointer {.importc, libUser32.} 454 | proc SetWindowLongA*(hWnd: pointer, nIndex, dwNewLong: int32): int32 {.importc, libUser32.} 455 | proc GetWindowLongA*(hWnd: pointer, nIndex: int32): int32 {.importc, libUser32.} 456 | proc SetWindowTextW*(hWnd: pointer, lpString: cstring): bool {.importc, libUser32.} 457 | proc GetWindowTextW*(hWnd: pointer, lpString: cstring, nMaxCount: int32): int32 {.importc, libUser32.} 458 | proc GetWindowTextLengthW*(hWnd: pointer): int32 {.importc, libUser32.} 459 | proc UpdateWindow*(hWnd: pointer): bool {.importc, libUser32.} 460 | proc SetWindowPos*(wnd, hWndInsertAfter: pointer, x, y, cx, cy: int32, uFlags: int): bool {.importc, libUser32.} 461 | proc MoveWindow*(wnd: pointer, x, y, nWidth, nHeight: int32, bRepaint: bool): bool {.importc, libUser32.} 462 | proc SetFocus*(hWnd: pointer): pointer {.importc, libUser32.} 463 | proc GetWindowRect*(wnd: pointer, lpRect: var Rect): bool {.importc, libUser32.} 464 | proc GetClientRect*(wnd: pointer, lpRect: var Rect): bool {.importc, libUser32.} 465 | proc BeginPaint*(hWnd: pointer, lpPaint: var PaintStruct): pointer {.importc, libUser32.} 466 | proc EndPaint*(hWnd: pointer, lpPaint: var PaintStruct): bool {.importc, libUser32.} 467 | proc SendMessageA*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc, libUser32.} 468 | proc SendMessageW*(hWnd: pointer, msg: int32, wParam, lParam: pointer): pointer {.importc, libUser32.} 469 | proc PostMessageA*(hWnd: pointer, msg: int32, wParam, lParam: pointer): bool {.importc, libUser32.} 470 | proc GetSysColor*(nIndex: int32): RGB32 {.importc, libUser32.} 471 | proc InvalidateRect*(hWnd: pointer, lpRect: ref Rect, bErase: bool): bool {.importc, libUser32.} 472 | proc PostQuitMessage*(nExitCode: int32) {.importc, libUser32.} 473 | proc GetDesktopWindow*(): pointer {.importc, libUser32.} 474 | # proc SystemParametersInfoW*(uiAction, uiParam: int32, pvParam: pointer, fWinIni: int32): bool {.importc, libUser32.} 475 | proc ClientToScreen*(hWnd: pointer, lpPoint: var Point): bool {.importc, libUser32.} 476 | proc AdjustWindowRect*(lpRect: var Rect, dwStyle: int32, bMenu: bool): bool {.importc, libUser32.} 477 | proc LoadCursorA*(hInstance: pointer, lpCursorName: cstring): pointer {.importc, libUser32.} 478 | proc SetScrollInfo*(hWnd: pointer, fnBar: int32, lpsi: var ScrollInfo, fRedraw: bool): int32 {.importc, libUser32.} 479 | proc GetMonitorInfoA*(hMonitor: pointer, lpmi: var MonitorInfo): bool {.importc, libUser32.} 480 | proc MonitorFromRect*(lprc: var Rect, dwFlags: int32): pointer {.importc, libUser32.} 481 | proc GetSystemMetrics*(nIndex: int32): int32 {.importc, libUser32.} 482 | proc CallWindowProcW*(lpPrevWndFunc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.importc, libUser32.} 483 | proc IsDialogMessageW*(hDlg, lpMsg: pointer): bool {.importc, libUser32.} 484 | proc GetNextDlgTabItem*(hDlg, hCtl: pointer, bPrevious: bool): pointer {.importc, libUser32.} 485 | proc GetParent*(hWnd: pointer): pointer {.importc, libUser32.} 486 | proc GetDC*(hWnd: pointer): pointer {.importc, libUser32.} 487 | # proc DrawTextW*(hdc: pointer, lpchText: cstring, nCount: int32, lpRect: var Rect, uFormat: int32): int32 {.importc, libUser32.} 488 | proc GetKeyboardState*(lpKeyState: var KeyState): bool {.importc, libUser32.} 489 | proc ToUnicode*(wVirtKey, wScanCode: int32, lpKeyState: var KeyState, pwszBuff: cstring, cchBuff, wFlags: int32): int32 {.importc, libUser32.} 490 | proc ShowScrollBar*(hWnd: pointer, wBar: int32, bShow: bool): bool {.importc, libUser32.} 491 | proc LoadImageW*(hinst: pointer, lpszName: cstring, uType, cxDesired, cyDesired, fuLoad: int32): int32 {.importc, libUser32.} 492 | proc SetTimer*(hWnd, nIDEvent: pointer, uElapse: int32, lpTimerFunc: pointer): pointer {.importc, libUser32.} 493 | proc KillTimer*(hWnd, nIDEvent: pointer): bool {.importc, libUser32.} 494 | proc FillRect*(hDC: pointer, lprc: var Rect, hbr: pointer): int32 {.importc, libUser32.} 495 | proc FrameRect*(hDC: pointer, lprc: var Rect, hbr: pointer): int32 {.importc, libUser32.} 496 | proc GetKeyState*(nVirtKey: int32): int16 {.importc, libUser32.} 497 | proc OpenClipboard*(hWndNewOwner: pointer): bool {.importc, libUser32.} 498 | proc CloseClipboard*(): bool {.importc, libUser32.} 499 | proc GetClipboardData*(uFormat: int32): pointer {.importc, libUser32.} 500 | proc SetClipboardData*(uFormat: int32, hMem: pointer): pointer {.importc, libUser32.} 501 | proc EmptyClipboard*(): bool {.importc, libUser32.} 502 | proc MapVirtualKeyW*(uCode, uMapType: int32): int32 {.importc, libUser32.} 503 | proc GetCursorPos*(lpPoint: var Point): bool {.importc, libUser32.} 504 | proc ScreenToClient*(hWnd: pointer, lpPoint: var Point): bool {.importc, libUser32.} 505 | proc MonitorFromPoint*(pt: Point, dwFlags: int32): pointer {.importc, libUser32.} 506 | proc GetScrollPos*(hWnd: pointer, nBar: int32): int32 {.importc, libUser32.} 507 | proc LockWindowUpdate*(hWndLock: pointer): bool {.importc, libUser32.} 508 | proc TrackMouseEvent*(lpEventTrack: var TTrackMouseEvent): bool {.importc, libUser32.} 509 | 510 | type GetDpiForWindowType* = proc(hWnd: pointer): int32 {.gcsafe, stdcall.} # not available on Windows 7 511 | 512 | when defined(cpu64): 513 | # Only available on 64-bit Windows: 514 | proc GetWindowLongPtrW*(hWnd: pointer, nIndex: int32): pointer {.importc, libUser32.} 515 | proc SetWindowLongPtrW*(hWnd: pointer, nIndex: int32, dwNewLong: pointer): pointer {.importc, libUser32.} 516 | else: 517 | # Should only be used on 32-bit Windows: 518 | proc GetWindowLongW*(hWnd: pointer, nIndex: int32): pointer {.importc, libUser32.} 519 | proc SetWindowLongW*(hWnd: pointer, nIndex: int32, dwNewLong: pointer): pointer {.importc, libUser32.} 520 | 521 | 522 | # ---------------------------------------------------------------------------------------- 523 | # GDI Procs 524 | # ---------------------------------------------------------------------------------------- 525 | 526 | proc DeleteDC*(hdc: pointer): bool {.importc, libGdi32.} 527 | proc DeleteObject*(hObject: pointer): bool {.importc, libGdi32.} 528 | proc GetCurrentObject*(hdc: pointer, uObjectType: int32): pointer {.importc, libGdi32.} 529 | proc SelectObject*(hdc, hgdiobj: pointer): pointer {.importc, libGdi32.} 530 | proc TextOutW*(hdc: pointer, nXStart, nYStart: int32, lpString: cstring, cchString: int32): bool {.importc, libGdi32.} 531 | proc CreateSolidBrush*(crColor: RGB32): pointer {.importc, libGdi32.} 532 | proc CreatePen*(fnPenStyle, nWidth: int32, crColor: RGB32): pointer {.importc, libGdi32.} 533 | # proc GetStockObject*(fnObject: int32): pointer {.importc, libGdi32.} 534 | proc CreateFontW*(nHeight, nWidth, nEscapement, nOrientation, fnWeight, fdwItalic, fdwUnderline, fdwStrikeOut, fdwCharSet, fdwOutputPrecision, fdwClipPrecision, fdwQuality, fdwPitchAndFamily: int32, lpszFace: cstring): pointer {.importc, libGdi32.} 535 | proc GetTextExtentPoint32W*(hdc: pointer, lpString: cstring, c: int32, lpSize: var Size): bool {.importc, libGdi32.} 536 | proc SetBkMode*(hdc: pointer, iBkMode: int32): int32 {.importc, libGdi32.} 537 | proc SetTextColor*(hdc: pointer, crColor: RGB32): RGB32 {.importc, libGdi32.} 538 | proc SetBkColor*(hdc: pointer, crColor: RGB32): RGB32 {.importc, libGdi32.} 539 | proc MoveToEx*(hdc: pointer, x, y: int32, lpPoint: pointer): bool {.importc, libGdi32.} 540 | proc LineTo*(hdc: pointer, nXEnd, nYEnd: int): bool {.importc, libGdi32.} 541 | proc CreateCompatibleDC*(hdc: pointer): pointer {.importc, libGdi32.} 542 | proc SetPixel*(hdc: pointer, x, y: int32, crColor: RGB32): int32 {.importc, libGdi32.} 543 | # proc BitBlt*(hdcDest: pointer, nXDest, nYDest, nWidth, nHeight: int32, hdcSrc: pointer, nXSrc, nYSrc, dwRop: int32): bool {.importc, libGdi32.} 544 | 545 | 546 | # ---------------------------------------------------------------------------------------- 547 | # GDI+ Procs 548 | # ---------------------------------------------------------------------------------------- 549 | 550 | proc GdiplusStartup*(token: var pointer, input: var GdiplusStartupInput, output: pointer): int32 {.importc, libGdiplus.} 551 | proc GdipCreateBitmapFromFile*(filename: cstring, bitmap: var pointer): int32 {.importc, libGdiplus.} 552 | # proc GdipLoadImageFromFile*(filename: cstring, image: var pointer): int32 {.importc, libGdiplus.} 553 | proc GdipCreateHICONFromBitmap*(bitmap: pointer, hicon: var pointer): int32 {.importc, libGdiplus.} 554 | proc GdipCreateFromHDC*(hdc: pointer, graphics: var pointer): int32 {.importc, libGdiplus.} 555 | proc GdipDeleteGraphics*(graphics: pointer): int32 {.importc, libGdiplus.} 556 | proc GdipDrawImageRectI*(graphics, image: pointer, x, y, width, height: int32): int32 {.importc, libGdiplus.} 557 | proc GdipGetImageWidth*(image: pointer, width: var int32): int32 {.importc, libGdiplus.} 558 | proc GdipGetImageHeight*(image: pointer, height: var int32): int32 {.importc, libGdiplus.} 559 | # proc GdipGetImageDimension*(image: pointer, width, height: var float): int32 {.importc, libGdiplus.} 560 | proc GdipCreateBitmapFromGraphics*(width, height: int32, target: pointer, bitmap: var pointer): int32 {.importc, libGdiplus.} 561 | proc GdipBitmapSetPixel*(bitmap: pointer, x, y: int32, color: ARGB): int32 {.importc, libGdiplus.} 562 | proc GdipSaveImageToFile*(image: pointer, filename: cstring, clsidEncoder, encoderParams: pointer): int32 {.importc, libGdiplus.} 563 | # proc GdipGetImageEncodersSize*(numEncoders, size: var int32): int32 {.importc, libGdiplus.} 564 | # proc GdipGetImageEncoders*(numEncoders, size: int32, encoders: pointer): int32 {.importc, libGdiplus.} 565 | proc GdipGetImageGraphicsContext*(image: pointer, graphics: var pointer): int32 {.importc, libGdiplus.} # does not exist 566 | proc GdipDisposeImage*(image: pointer): int32 {.importc, libGdiplus.} 567 | proc GdipFillRectangleI*(graphics, brush: pointer, x, y, width, height: int32): int32 {.importc, libGdiplus.} 568 | proc GdipDrawRectangleI*(graphics, pen: pointer, x, y, width, height: int32): int32 {.importc, libGdiplus.} 569 | proc GdipDrawLineI*(graphics, pen: pointer, x1, y1, x2, y2: int32): int32 {.importc, libGdiplus.} 570 | proc GdipDrawArc*(graphics, pen: pointer, x, y, width, height, startAngle, sweepAngle: cfloat): int32 {.importc, libGdiplus.} 571 | proc GdipFillEllipseI*(graphics, brush: pointer, x, y, width, height: int32): int32 {.importc, libGdiplus.} 572 | proc GdipDrawEllipseI*(graphics, pen: pointer, x, y, width, height: int32): int32 {.importc, libGdiplus.} 573 | proc GdipCreateSolidFill*(color: ARGB, brush: var pointer): int32 {.importc, libGdiplus.} 574 | proc GdipDeleteBrush*(brush: pointer): int32 {.importc, libGdiplus.} 575 | proc GdipCreatePen1*(color: ARGB, width: cfloat, unit: int32, pen: var pointer): int32 {.importc, libGdiplus.} 576 | proc GdipDeletePen*(pen: pointer): int32 {.importc, libGdiplus.} 577 | proc GdipDrawString*(graphics: pointer, `string`: cstring, length: int32, font: pointer, layoutRect: var RectF, stringFormat, brush: pointer): int32 {.importc, libGdiplus.} 578 | proc GdipMeasureString*(graphics: pointer, `string`: cstring, length: int32, font: pointer, layoutRect: var RectF, stringFormat: pointer, boundingBox: var RectF, codepointsFitted, linesFilled: pointer): int32 {.importc, libGdiplus.} 579 | proc GdipCreateFont*(fontFamily: pointer, emSize: cfloat, style, unit: int32, font: var pointer): int32 {.importc, libGdiplus.} 580 | proc GdipDeleteFont*(font: pointer): int32 {.importc, libGdiplus.} 581 | proc GdipCreateFontFamilyFromName*(name: cstring, fontCollection: pointer, fontFamily: var pointer): int32 {.importc, libGdiplus.} 582 | proc GdipDeleteFontFamily*(fontFamily: pointer): int32 {.importc, libGdiplus.} 583 | proc GdipBitmapLockBits*(bitmap: pointer, rect: var Rect, flags: int32, format: int32, lockedBitmapData: var BitmapData): int32 {.importc, libGdiplus.} 584 | proc GdipBitmapUnlockBits*(bitmap: pointer, lockedBitmapData: var BitmapData): int32 {.importc, libGdiplus.} 585 | proc GdipSetTextRenderingHint*(graphics: pointer, mode: int32): int32 {.importc, libGdiplus.} 586 | proc GdipSetInterpolationMode*(graphics: pointer, interpolationMode: int32): int32 {.importc, libGdiplus.} 587 | 588 | 589 | # ---------------------------------------------------------------------------------------- 590 | # Shell32 Procs 591 | # ---------------------------------------------------------------------------------------- 592 | 593 | proc DragAcceptFiles*(hWnd: pointer, fAccept: bool) {.importc, libShell32.} 594 | proc DragQueryFileW*(hDrop: pointer, iFile: uint32, lpszFile: cstring, cch: int32): int32 {.importc, libShell32.} 595 | proc DragFinish*(hDrop: pointer) {.importc, libShell32.} 596 | proc SHBrowseForFolderW*(lpbi: var BrowseInfo): pointer {.importc, libShell32.} 597 | proc SHGetPathFromIDListW*(pidl: pointer, pszPath: cstring) {.importc, libShell32.} 598 | 599 | 600 | # ---------------------------------------------------------------------------------------- 601 | # Comdlg32 Procs 602 | # ---------------------------------------------------------------------------------------- 603 | 604 | proc CommDlgExtendedError*(): int32 {.importc, libComdlg32.} 605 | proc GetOpenFileNameW*(lpofn: var OpenFileName): bool {.importc, libComdlg32.} 606 | proc GetSaveFileNameW*(lpofn: var OpenFileName): bool {.importc, libComdlg32.} 607 | 608 | 609 | # ---------------------------------------------------------------------------------------- 610 | # Shcore Procs 611 | # ---------------------------------------------------------------------------------------- 612 | 613 | type SetProcessDpiAwarenessType* = proc(value: int32): int32 {.gcsafe, stdcall.} # not available on Windows 7 614 | --------------------------------------------------------------------------------