├── .editorconfig ├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── build-libui.sh ├── examples ├── control_gallery.cr └── histogram.cr ├── shard.yml ├── spec ├── cru_spec.cr └── spec_helper.cr ├── src ├── cru.cr ├── cru │ ├── app.cr │ ├── area.cr │ ├── box.cr │ ├── button.cr │ ├── checkbox.cr │ ├── combo_box.cr │ ├── control.cr │ ├── date_time_picker.cr │ ├── entry.cr │ ├── form.cr │ ├── group.cr │ ├── label.cr │ ├── menu.cr │ ├── multiline_entry.cr │ ├── progress_bar.cr │ ├── radio_buttons.cr │ ├── separator.cr │ ├── slider.cr │ ├── spinbox.cr │ ├── tab.cr │ ├── types │ │ ├── draw_brush.cr │ │ ├── draw_matrix.cr │ │ ├── draw_path.cr │ │ └── font.cr │ ├── version.cr │ └── window.cr └── ext │ └── time.cr └── static └── control_gallery_screenshot.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cr] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | *.dwarf 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in applications that use them 9 | /shard.lock 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/libui-ng"] 2 | path = vendor/libui-ng 3 | url = https://github.com/libui-ng/libui-ng.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | 3 | # Uncomment the following if you'd like Travis to run specs and check code formatting 4 | # script: 5 | # - crystal spec 6 | # - crystal tool format --check 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Chris Watzon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cru 2 | 3 | Cru is a basic UI framework based off of [Fusion/libui](https://github.com/Fusion/libui.cr) and [libui](https://github.com/andlabs/libui). Right now it's pretty barebones, and libui itself is still "mid alpha" software, but I hope it can eventually become a pretty decent GUI framework for Crystal. 4 | 5 |
6 | screenshot 7 |
8 | 9 | ## Installation 10 | 11 | 1. Install [libui](https://github.com/andlabs/libui) 12 | 13 | 2. Add the dependency to your `shard.yml`: 14 | 15 | ```yaml 16 | dependencies: 17 | cru: 18 | github: watzon/cru 19 | ``` 20 | 21 | 3. Run `shards install` 22 | 23 | ## Usage 24 | 25 | Check the [control_gallery](./examples/control_gallery.cr) example in the /examples folder. 26 | 27 | ## Contributing 28 | 29 | 1. Fork it () 30 | 2. Create your feature branch (`git checkout -b my-new-feature`) 31 | 3. Commit your changes (`git commit -am 'Add some feature'`) 32 | 4. Push to the branch (`git push origin my-new-feature`) 33 | 5. Create a new Pull Request 34 | 35 | ## Contributors 36 | 37 | - [Chris Watzon](https://github.com/watzon) - creator and maintainer 38 | -------------------------------------------------------------------------------- /build-libui.sh: -------------------------------------------------------------------------------- 1 | startdir=$(pwd) 2 | 3 | # cd to the path of the script 4 | cd "$(dirname "$0")" 5 | 6 | # update the submodules 7 | git submodule update --init --recursive 8 | 9 | # Path to the libui source code 10 | cd vendor/libui-ng 11 | 12 | # Build libui 13 | meson setup build 14 | ninja -C build install 15 | 16 | # cd back to the original directory 17 | cd $startdir -------------------------------------------------------------------------------- /examples/control_gallery.cr: -------------------------------------------------------------------------------- 1 | # This is the standard control gallery, based off of the one 2 | # at https://github.com/Fusion/libui.cr/blob/master/examples/controlgallery/main.cr 3 | 4 | require "../src/cru" 5 | include Cru 6 | 7 | # Bootstrap the app 8 | app = App.new 9 | app.should_quit.on do 10 | exit(0) 11 | end 12 | 13 | file_menu = Menu.new "File" 14 | 15 | open_item = file_menu.append_item "Open" 16 | open_item.clicked.on do |window| 17 | filename = window.open_file 18 | if !filename 19 | window.show_error("No file selected!", "This is an error.") 20 | else 21 | window.show_message("File selected", filename) 22 | end 23 | end 24 | 25 | save_item = file_menu.append_item "Save" 26 | save_item.clicked.on do |window| 27 | filename = window.save_file 28 | if !filename 29 | window.show_error("No file selected!", "This is an error.") 30 | else 31 | window.show_message("File selected", filename) 32 | end 33 | end 34 | 35 | file_menu.append_quit_item 36 | 37 | edit_meu = Menu.new "Edit" 38 | edit_meu.append_check_item "Checkable Item" 39 | disabled_item = edit_meu.append_item "Disabled Item" 40 | disabled_item.disable 41 | edit_meu.append_preferences_item 42 | 43 | help_menu = Menu.new "Help" 44 | help_menu.append_item "Help" 45 | help_menu.append_about_item 46 | 47 | window = Window.new "Cru Control Gallery", 640, 480, true 48 | window.margined = true 49 | 50 | box = VerticalBox.new 51 | box.padded = true 52 | window.child = box 53 | 54 | hbox = HorizontalBox.new 55 | hbox.padded = true 56 | box.append hbox, true 57 | 58 | group = Group.new "Basic Controls" 59 | group.margined = true 60 | hbox.append group 61 | 62 | inner = VerticalBox.new 63 | inner.padded = true 64 | group.child = inner 65 | 66 | inner.append Button.new("Button") 67 | inner.append Checkbox.new("Checkbox") 68 | entry = Entry.new 69 | entry.text = "Entry" 70 | inner.append entry 71 | inner.append Label.new("Label") 72 | 73 | inner.append HorizontalSeparator.new 74 | 75 | inner.append DatePicker.new 76 | inner.append TimePicker.new 77 | inner.append DateTimePicker.new 78 | 79 | inner.append FontButton.new 80 | inner.append ColorButton.new 81 | 82 | inner2 = VerticalBox.new 83 | inner2.padded = true 84 | hbox.append inner2, true 85 | 86 | group = Group.new "Numbers" 87 | group.margined = true 88 | inner2.append group 89 | 90 | inner = VerticalBox.new 91 | inner.padded = true 92 | group.child = inner 93 | 94 | progress_bar = ProgressBar.new 95 | spinbox = Spinbox.new 0, 100 96 | slider = Slider.new 0, 100 97 | 98 | spinbox.changed.on do 99 | value = spinbox.value.to_i 100 | slider.value = value 101 | progress_bar.value = value 102 | end 103 | 104 | slider.changed.on do 105 | value = slider.value.to_i 106 | spinbox.value = value 107 | progress_bar.value = value 108 | end 109 | 110 | inner.append spinbox 111 | inner.append slider 112 | inner.append progress_bar 113 | 114 | group = Group.new "Lists" 115 | group.margined = true 116 | inner2.append group 117 | 118 | inner = VerticalBox.new 119 | inner.padded = true 120 | group.child = inner 121 | 122 | cbox = ComboBox.new([ 123 | "Item 1", 124 | "Item 2", 125 | "Item 3", 126 | ]) 127 | inner.append cbox 128 | 129 | ecbox = EditableComboBox.new([ 130 | "Item 1", 131 | "Item 2", 132 | "Item 3", 133 | ]) 134 | inner.append ecbox 135 | 136 | rb = RadioButtons.new([ 137 | "Button 1", 138 | "Button 2", 139 | "Button 3", 140 | ]) 141 | inner.append rb, true 142 | 143 | tab = Tab.new 144 | tab.append "Page 1", HorizontalBox.new 145 | tab.append "Page 2", HorizontalBox.new 146 | tab.append "Page 3", HorizontalBox.new 147 | inner2.append tab, true 148 | 149 | window.show 150 | 151 | app.start 152 | -------------------------------------------------------------------------------- /examples/histogram.cr: -------------------------------------------------------------------------------- 1 | require "../src/cru" 2 | 3 | class Histogram < Cru::Area 4 | XOffLeft = 20.0 5 | YOffTop = 20.0 6 | XOffRight = 20.0 7 | YOffBottom = 20.0 8 | PointRadius = 5.0 9 | 10 | ColorWhite = 0xFFFFFF 11 | ColorBlack = 0x000000 12 | ColorDodgerBlue = 0x1E90FF 13 | 14 | getter datapoints : Array(Int32) 15 | 16 | def initialize(@datapoints) 17 | super() 18 | end 19 | 20 | def on_draw(params : UI::AreaDrawParams) 21 | brush = Cru::DrawBrush.new(ColorWhite, 1.0) 22 | 23 | path = Cru::DrawPath.new(:winding) 24 | path.add_rectangle(0, 0, params.area_width, params.area_height) 25 | path.end 26 | path.fill(params.context, brush) 27 | 28 | graph_width = params.area_width - XOffLeft - XOffRight 29 | graph_height = params.area_height - YOffTop - YOffBottom 30 | 31 | brush.color = ColorBlack 32 | path = Cru::DrawPath.new(:winding) 33 | path.new_figure(XOffLeft, YOffTop) 34 | path.line_to(XOffLeft, YOffTop + graph_height) 35 | path.line_to(XOffLeft + graph_width, YOffTop + graph_height) 36 | path.end 37 | path.stroke(params.context, brush, thickness: 2.0) 38 | 39 | matrix = Cru::DrawMatrix.new 40 | matrix.translate(XOffLeft, YOffTop) 41 | matrix.transform(params.context) 42 | 43 | # color_button = Cru::ColorButton.new 44 | # color_button.changed.on do 45 | # graph_colors = color_button.color 46 | # brush.r = graph_colors[:red] 47 | # brush.g = graph_colors[:green] 48 | # brush.b = graph_colors[:blue] 49 | # end 50 | 51 | path = construct_graph(graph_width, graph_height, true) 52 | path.fill(params.context, brush) 53 | end 54 | 55 | def on_mouse_event(mouse_event : UI::AreaMouseEvent) 56 | end 57 | 58 | def on_mouse_crossed(left : Bool) 59 | end 60 | 61 | def on_drag_broken 62 | end 63 | 64 | def on_key_event(key_event : UI::AreaKeyEvent) : Int32 65 | return 0 66 | end 67 | 68 | def on_datapoint_changed 69 | 70 | end 71 | 72 | def construct_graph(width, height, ext) 73 | path = Cru::DrawPath.new(:winding) 74 | xs = @datapoints.dup.map{ 0.0 } 75 | ys = @datapoints.dup.map{ 0.0 } 76 | 77 | point_locations(width, height, xs, ys) 78 | 79 | path.new_figure(xs[0], ys[0]) 80 | (1...@datapoints.size).each do |i| 81 | path.line_to(xs[i], ys[i]) 82 | end 83 | 84 | if ext 85 | path.line_to(width, height) 86 | path.line_to(0, height) 87 | path.close_figure 88 | end 89 | 90 | path.end 91 | path 92 | end 93 | 94 | def point_locations(width, height, xs, ys) 95 | xincr = width / 9 96 | yincr = height / 100 97 | 98 | @datapoints.each_with_index do |n, i| 99 | # Because y=0 is the top but n=0 is the bottom, we need to flip 100 | n = 100 - n 101 | xs[i] = xincr * i 102 | ys[i] = yincr * n 103 | end 104 | end 105 | end 106 | 107 | app = Cru::App.new 108 | window = Cru::Window.new("Histogram", 800, 600, false) 109 | 110 | box = Cru::VerticalBox.new 111 | box.padded = true 112 | window.child = box 113 | 114 | hbox = Cru::HorizontalBox.new 115 | hbox.padded = true 116 | box.append hbox, true 117 | 118 | group = Cru::Group.new "Histogram" 119 | group.margined = true 120 | hbox.append group, true 121 | 122 | inner = Cru::VerticalBox.new 123 | inner.padded = true 124 | group.child = inner 125 | 126 | datapoints = [0, 23, 12, 43, 35, 32, 65, 87, 23] 127 | histogram = Histogram.new(datapoints) 128 | inner.append histogram, true 129 | 130 | window.show 131 | app.start 132 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: cru 2 | version: 0.1.0 3 | 4 | authors: 5 | - Chris Watzon 6 | 7 | crystalline: 8 | main: examples/histogram.cr 9 | 10 | dependencies: 11 | libui: 12 | # github: watzon/libui.cr 13 | # branch: master 14 | path: ../libui.cr 15 | cute: 16 | github: Papierkorb/cute 17 | branch: master 18 | 19 | crystal: 0.34.0 20 | 21 | license: MIT 22 | -------------------------------------------------------------------------------- /spec/cru_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Cru do 4 | # TODO: Write tests 5 | 6 | it "works" do 7 | false.should eq(true) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/cru" 3 | -------------------------------------------------------------------------------- /src/cru.cr: -------------------------------------------------------------------------------- 1 | require "cute" 2 | require "libui" 3 | 4 | require "./ext/*" 5 | 6 | require "./cru/app" 7 | require "./cru/types/*" 8 | require "./cru/*" 9 | 10 | module Cui 11 | end 12 | -------------------------------------------------------------------------------- /src/cru/app.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | class App 3 | Cute.signal should_quit(app : App) 4 | 5 | class_getter draw_context : Pointer(UI::DrawContext) { Pointer(UI::DrawContext).malloc(1) } 6 | 7 | # Create a new Cru app 8 | def initialize 9 | options = UI::InitOptions.new 10 | err = UI.init pointerof(options) 11 | 12 | if !ui_nil?(err) 13 | puts "Failed to initialize Cru application: #{err}" 14 | exit 1 15 | end 16 | 17 | set_on_should_quit { should_quit.emit(self); 0 } 18 | end 19 | 20 | # Start the application main loop 21 | def start 22 | UI.main 23 | end 24 | 25 | # Stop the application main loop 26 | def stop 27 | UI.quit 28 | end 29 | 30 | # Close the application and free resources 31 | def close 32 | UI.uninit 33 | end 34 | 35 | @on_should_quit_cb : Proc(Int32)? 36 | private def set_on_should_quit(&cb : -> Int32) 37 | @on_should_quit_cb = cb 38 | UI.on_should_quit ->(data) { 39 | data.as(typeof(cb)*).value.call 40 | }, pointerof(@on_should_quit_cb) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/cru/area.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | abstract class Area < Control 5 | @on_draw_cb : Proc(UI::AreaHandler*, UI::Area*, UI::AreaDrawParams*, Nil) 6 | @on_mouse_event_cb : Proc(UI::AreaHandler*, UI::Area*, UI::AreaMouseEvent*, Nil) 7 | @on_mouse_crossed_cb : Proc(UI::AreaHandler*, UI::Area*, LibC::Int, Nil) 8 | @on_drag_broken_cb : Proc(UI::AreaHandler*, UI::Area*, Nil) 9 | @on_key_event_cb : Proc(UI::AreaHandler*, UI::Area*, UI::AreaKeyEvent*, Nil) 10 | 11 | def initialize(width = nil, height = width) 12 | @on_draw_cb = ->(h : UI::AreaHandler*, a : UI::Area*, p : UI::AreaDrawParams*) { 13 | on_draw(p.value) 14 | nil 15 | } 16 | 17 | @on_mouse_event_cb = ->(h : UI::AreaHandler*, a : UI::Area*, e : UI::AreaMouseEvent*) { 18 | on_mouse_event(e.value) 19 | nil 20 | } 21 | 22 | @on_mouse_crossed_cb = ->(h : UI::AreaHandler*, a : UI::Area*, left : LibC::Int) { 23 | on_mouse_crossed(left == 1) 24 | nil 25 | } 26 | 27 | @on_drag_broken_cb = ->(h : UI::AreaHandler*, a : UI::Area*) { 28 | on_drag_broken 29 | nil 30 | } 31 | 32 | @on_key_event_cb = ->(h : UI::AreaHandler*, a : UI::Area*, e : UI::AreaKeyEvent*) { 33 | on_key_event(e.value) 34 | nil 35 | } 36 | 37 | @handler = UI::AreaHandler.new 38 | @handler.draw = @on_draw_cb 39 | @handler.mouse_event = @on_mouse_event_cb 40 | @handler.mouse_crossed = @on_mouse_crossed_cb 41 | @handler.drag_broken = @on_drag_broken_cb 42 | @handler.key_event = @on_key_event_cb 43 | 44 | if width && height 45 | @area = UI.new_scrolling_area(pointerof(@handler), width, height) 46 | else 47 | @area = UI.new_area(pointerof(@handler)) 48 | end 49 | 50 | super(@area.not_nil!) 51 | end 52 | 53 | abstract def on_draw(params : UI::AreaDrawParams) 54 | abstract def on_mouse_event(mouse_event : UI::AreaMouseEvent) 55 | abstract def on_mouse_crossed(left : Bool) 56 | abstract def on_drag_broken 57 | abstract def on_key_event(key_event : UI::AreaKeyEvent) : Int32 58 | 59 | def set_size(width : Int32, height : Int32) 60 | UI.area_set_size(@area, width, height) 61 | end 62 | 63 | def redraw_all 64 | UI.area_queue_redraw_all(@area) 65 | end 66 | 67 | def scroll_to(x : Float64, y : Float64, width : Float64, height : Float64) 68 | UI.area_scroll_to(@area, x, y, width, height) 69 | end 70 | 71 | # Can only be used within the `on_mouse_event` and `on_mouse_crossed` handlers. 72 | def begin_user_window_move 73 | UI.area_begin_user_window_move(@area) 74 | end 75 | 76 | # Can only be used within the `on_mouse_event` and `on_mouse_crossed` handlers. 77 | def begin_user_window_resize(edge : UI::WindowResizeEdge) 78 | UI.area_begin_user_window_resize(@area) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /src/cru/box.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | abstract class Box < Control 5 | getter children : Array(Control) 6 | 7 | def initialize(@box : UI::Box*) 8 | super(@box) 9 | @children = [] of Control 10 | end 11 | 12 | # Appends a child to the box. 13 | def append(child, stretchy = false) 14 | UI.box_append(@box, child, stretchy) 15 | @children << child 16 | end 17 | 18 | # Delete a child from the box. 19 | def delete(index) 20 | UI.box_delete(@box, index) 21 | @children[index].destroy 22 | @children.delete_at(index) 23 | end 24 | 25 | # Returns true if this box is padded. 26 | def padded? 27 | UI.box_padded(@box) 28 | end 29 | 30 | # Sets whether this box is padded. 31 | def padded=(value : Bool) 32 | UI.box_set_padded(@box, value) 33 | end 34 | end 35 | 36 | class HorizontalBox < Box 37 | def initialize 38 | @box = UI.new_horizontal_box 39 | super(@box) 40 | end 41 | end 42 | 43 | class VerticalBox < Box 44 | def initialize 45 | @box = UI.new_vertical_box 46 | super(@box) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/cru/button.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Button < Control 5 | Cute.signal clicked 6 | 7 | def initialize(text : String) 8 | @button = UI.new_button(text) 9 | super(@button) 10 | set_on_clicked { clicked.emit } 11 | end 12 | 13 | def text 14 | String.new(UI.button_text(@button)) 15 | end 16 | 17 | def text=(value : String) 18 | UI.button_set_text(@button, value) 19 | end 20 | 21 | @on_clicked_cb : Proc(Nil)? 22 | private def set_on_clicked(&cb : ->) 23 | @on_clicked_cb = cb 24 | UI.button_on_clicked @button, ->(window, data) { 25 | data.as(typeof(cb)*).value.call 26 | }, pointerof(@on_clicked_cb) 27 | end 28 | end 29 | 30 | class FontButton < Control 31 | Cute.signal changed(button : FontButton) 32 | 33 | def initialize 34 | @button = UI.new_font_button 35 | super(@button) 36 | set_on_changed { changed.emit(self) } 37 | end 38 | 39 | def font 40 | UI.font_button_font(@button, out descriptor) 41 | Font.from_descriptor(descriptor) 42 | end 43 | 44 | @on_changed_cb : Proc(Nil)? 45 | private def set_on_changed(&cb : ->) 46 | @on_changed_cb = cb 47 | UI.font_button_on_changed @button, ->(button, data) { 48 | data.as(typeof(cb)*).value.call 49 | }, pointerof(@on_changed_cb) 50 | end 51 | end 52 | 53 | class ColorButton < Control 54 | Cute.signal changed(button : ColorButton) 55 | 56 | def initialize 57 | @button = UI.new_color_button 58 | super(@button) 59 | set_on_changed { changed.emit(self) } 60 | end 61 | 62 | def color 63 | UI.color_button_color(@button, out r, out g, out b, out a) 64 | { red: r, green: g, blue: b, alpha: a } 65 | end 66 | 67 | def set_color(red, green, blue, alpha) 68 | UI.color_button_set_color(@button, red, green, blue, alpha) 69 | end 70 | 71 | @on_changed_cb : Proc(Nil)? 72 | private def set_on_changed(&cb : ->) 73 | @on_changed_cb = cb 74 | UI.color_button_on_changed @button, ->(button, data) { 75 | data.as(typeof(cb)*).value.call 76 | }, pointerof(@on_changed_cb) 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /src/cru/checkbox.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Checkbox < Control 5 | Cute.signal toggled(checkbox : Checkbox) 6 | 7 | def initialize(text : String) 8 | @checkbox = UI.new_checkbox(text) 9 | super(@checkbox) 10 | set_on_toggled { toggled.emit(self) } 11 | end 12 | 13 | def text 14 | String.new(UI.checkbox_text(@checkbox)) 15 | end 16 | 17 | def text=(value : String) 18 | UI.checkbox_set_text(@checkbox, value) 19 | end 20 | 21 | def checked? 22 | UI.checkbox_checked(@checkbox) > 0 23 | end 24 | 25 | def checked=(value : Bool) 26 | UI.checkbox_set_checked(@checkbox, value) 27 | end 28 | 29 | @on_toggled_cb : Proc(Nil)? 30 | private def set_on_toggled(&cb : ->) 31 | @on_toggled_cb = cb 32 | UI.checkbox_on_toggled @checkbox, ->(window, data) { 33 | data.as(typeof(cb)*).value.call 34 | }, pointerof(@on_toggled_cb) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /src/cru/combo_box.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class ComboBox < Control 5 | Cute.signal selected(combobox : ComboBox) 6 | 7 | def initialize(items = [] of String) 8 | @combobox = UI.new_combobox() 9 | super(@combobox) 10 | items.each { |item| append(item) } 11 | set_on_selected { selected.emit(self) } 12 | end 13 | 14 | def append(item : String) 15 | UI.combobox_append(@combobox, item) 16 | end 17 | 18 | def selected_index 19 | UI.combobox_selected(@combobox) 20 | end 21 | 22 | def selected_index=(value : Int32) 23 | UI.combobox_set_selected(@combobox, value) 24 | end 25 | 26 | @on_selected_cb : Proc(Nil)? 27 | private def set_on_selected(&cb : ->) 28 | @on_selected_cb = cb 29 | UI.combobox_on_selected @combobox, ->(window, data) { 30 | data.as(typeof(cb)*).value.call 31 | }, pointerof(@on_selected_cb) 32 | end 33 | end 34 | 35 | class EditableComboBox < Control 36 | Cute.signal changed(combobox : EditableComboBox) 37 | 38 | def initialize(items = [] of String) 39 | @combobox = UI.new_editable_combobox() 40 | super(@combobox) 41 | items.each { |item| append(item) } 42 | set_on_changed { changed.emit(self) } 43 | end 44 | 45 | def append(item : String) 46 | UI.editable_combobox_append(@combobox, item) 47 | end 48 | 49 | def text 50 | String.new(UI.editable_combobox_text(@combobox)) 51 | end 52 | 53 | def text=(value : String) 54 | UI.editable_combobox_set_text(@combobox, value) 55 | end 56 | 57 | @on_changed_cb : Proc(Nil)? 58 | private def set_on_changed(&cb : ->) 59 | @on_changed_cb = cb 60 | UI.editable_combobox_on_changed @combobox, ->(window, data) { 61 | data.as(typeof(cb)*).value.call 62 | }, pointerof(@on_changed_cb) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /src/cru/control.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | # Represents a LibUI control 3 | abstract class Control 4 | private getter control : Pointer(UI::Control) 5 | 6 | def initialize(control) 7 | @control = ui_control(control) 8 | end 9 | 10 | # Returns true if the control is currently visible 11 | def visible? 12 | UI.control_visible(self) > 0 13 | end 14 | 15 | # Shows the control 16 | def show 17 | UI.control_show(self) 18 | end 19 | 20 | # Hides the control 21 | def hide 22 | UI.control_hide(self) 23 | end 24 | 25 | # Returns true if the control is enabled 26 | def enabled? 27 | UI.control_enabled(self) > 0 28 | end 29 | 30 | # Enables the control 31 | def enable 32 | UI.control_enable(self) 33 | end 34 | 35 | # Disables the control 36 | def disable 37 | UI.control_disable(self) 38 | end 39 | 40 | # Destroys the control 41 | def destroy 42 | UI.control_destroy(self) 43 | end 44 | 45 | def to_unsafe 46 | self.control 47 | end 48 | 49 | def finalize 50 | self.destroy 51 | rescue 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/cru/date_time_picker.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class DateTimePicker < Control 5 | Cute.signal changed(picker : DateTimePicker) 6 | 7 | def initialize(@picker : UI::DateTimePicker*) 8 | super(@picker) 9 | set_on_changed { changed.emit(self) } 10 | end 11 | 12 | def self.new 13 | picker = UI.new_date_time_picker 14 | new(picker) 15 | end 16 | 17 | def time 18 | UI.date_time_picker_time(@picker, out tm) 19 | Time.from_tm_ptr(pointerof(tm)) 20 | end 21 | 22 | def time=(value : Time) 23 | tm = value.to_tm_ptr 24 | UI.date_time_picker_set_time(@picker, tm) 25 | end 26 | 27 | @on_changed_cb : Proc(Nil)? 28 | private def set_on_changed(&cb : ->) 29 | @on_changed_cb = cb 30 | UI.date_time_picker_on_changed @picker, ->(window, data) { 31 | data.as(typeof(cb)*).value.call 32 | }, pointerof(@on_changed_cb) 33 | end 34 | end 35 | 36 | class DatePicker < DateTimePicker 37 | def self.new 38 | picker = UI.new_date_picker 39 | new(picker) 40 | end 41 | end 42 | 43 | class TimePicker < DateTimePicker 44 | def self.new 45 | picker = UI.new_time_picker 46 | new(picker) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/cru/entry.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Entry < Control 5 | Cute.signal changed(entry : Entry) 6 | 7 | def initialize(@entry : UI::Entry*) 8 | super(@entry) 9 | set_on_changed { changed.emit(self); 0 } 10 | end 11 | 12 | def self.new 13 | entry = UI.new_entry 14 | new(entry) 15 | end 16 | 17 | def text 18 | String.new(UI.entry_text(@entry)) 19 | end 20 | 21 | def text=(value : String) 22 | UI.entry_set_text(@entry, value) 23 | end 24 | 25 | def read_only? 26 | UI.entry_read_only(@entry) > 0 27 | end 28 | 29 | def read_only=(value : Bool) 30 | UI.entry_set_read_only(@entry, value) 31 | end 32 | 33 | @on_changed_cb : Proc(Nil)? 34 | private def set_on_changed(&cb : ->) 35 | @on_changed_cb = cb 36 | UI.entry_on_changed @entry, ->(window, data) { 37 | data.as(typeof(cb)*).value.call 38 | }, pointerof(@on_changed_cb) 39 | end 40 | end 41 | 42 | class PasswordEntry < Entry 43 | def self.new 44 | entry = UI.new_password_entry 45 | new(entry) 46 | end 47 | end 48 | 49 | class SearchEntry < Entry 50 | def self.new 51 | entry = UI.new_search_entry 52 | new(entry) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/cru/form.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Form < Control 5 | getter children : Array(Control) 6 | 7 | def initialize 8 | @form = UI.new_form 9 | super(@form) 10 | @children = [] of Control 11 | end 12 | 13 | # Appends a child to the form. 14 | def append(child, stretchy = false) 15 | UI.form_append(@form, child, stretchy) 16 | @children << child 17 | end 18 | 19 | # Delete a child from the form. 20 | def delete(index) 21 | UI.form_delete(@form, index) 22 | @children[index].destroy 23 | @children.delete_at(index) 24 | end 25 | 26 | # Returns true if this form is padded. 27 | def padded? 28 | UI.form_padded(@form) 29 | end 30 | 31 | # Sets whether this form is padded. 32 | def padded=(value : Bool) 33 | UI.form_set_padded(@form, value) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /src/cru/group.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Group < Control 5 | def initialize(title : String) 6 | @group = UI.new_group(title) 7 | super(@group) 8 | end 9 | 10 | def title 11 | String.new(UI.group_title(@group)) 12 | end 13 | 14 | def title=(value : String) 15 | UI.group_set_title(@group, value) 16 | end 17 | 18 | def child=(value : Control) 19 | UI.group_set_child(@group, value) 20 | end 21 | 22 | def margined? 23 | UI.group_margined(@group) > 0 24 | end 25 | 26 | def margined=(value : Bool) 27 | UI.group_set_margined(@group, value) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /src/cru/label.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Label < Control 5 | def initialize(text : String) 6 | @label = UI.new_label(text) 7 | super(@label) 8 | end 9 | 10 | def text 11 | String.new(UI.label_text(@label)) 12 | end 13 | 14 | def text=(value : String) 15 | UI.label_set_text(@label, value) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/cru/menu.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class MenuItem 5 | def initialize(@menu_item : UI::MenuItem*) 6 | end 7 | 8 | # Enable the menu item. 9 | def enable 10 | UI.menu_item_enable(@menu_item) 11 | end 12 | 13 | # Disable the menu item. 14 | def disable 15 | UI.menu_item_disable(@menu_item) 16 | end 17 | end 18 | 19 | class ClickableMenuItem < MenuItem 20 | Cute.signal clicked(window : Window) 21 | 22 | def initialize(menu_item) 23 | super(menu_item) 24 | set_on_clicked { |window| clicked.emit(window) } 25 | end 26 | 27 | @on_clicked_cb : Proc(Window, Nil)? 28 | private def set_on_clicked(&cb : Window ->) 29 | @on_clicked_cb = cb 30 | UI.menu_item_on_clicked @menu_item, ->(m, w, data) { 31 | win = Window.new(w) 32 | data.as(typeof(cb)*).value.call(win) 33 | }, pointerof(@on_clicked_cb) 34 | end 35 | end 36 | 37 | class CheckableMenuItem < MenuItem 38 | def initialize(menu_item) 39 | super(menu_item) 40 | end 41 | 42 | # Returns true if the menu item is checked. 43 | def checked? 44 | UI.menu_item_checked(@menu_item) > 0 45 | end 46 | 47 | # Set whether the menu item is checked. 48 | def checked=(value : Bool) 49 | UI.menu_item_set_checked(@menu_item, value) 50 | end 51 | end 52 | 53 | class Menu 54 | def initialize(name : String) 55 | @menu = UI.new_menu(name) 56 | end 57 | 58 | def append_item(name : String) 59 | ClickableMenuItem.new(UI.menu_append_item(@menu, name)) 60 | end 61 | 62 | def append_check_item(name : String) 63 | CheckableMenuItem.new(UI.menu_append_check_item(@menu, name)) 64 | end 65 | 66 | def append_quit_item 67 | MenuItem.new(UI.menu_append_quit_item(@menu)) 68 | end 69 | 70 | def append_preferences_item 71 | ClickableMenuItem.new(UI.menu_append_preferences_item(@menu)) 72 | end 73 | 74 | def append_about_item 75 | ClickableMenuItem.new(UI.menu_append_about_item(@menu)) 76 | end 77 | 78 | def append_separator 79 | UI.menu_append_separator(@menu) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /src/cru/multiline_entry.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class MultilineEntry < Control 5 | Cute.signal changed(entry : MultilineEntry) 6 | 7 | def initialize(@entry : UI::MultilineEntry*) 8 | super(@entry) 9 | set_on_changed { changed.emit(self); 0 } 10 | end 11 | 12 | def self.new 13 | entry = UI.new_multiline_entry 14 | new(entry) 15 | end 16 | 17 | def append(text : String) 18 | UI.multiline_entry_append(@entry, text) 19 | end 20 | 21 | def text 22 | String.new(UI.multiline_entry_text(@entry)) 23 | end 24 | 25 | def text=(value : String) 26 | UI.multiline_entry_set_text(@entry, value) 27 | end 28 | 29 | def read_only? 30 | UI.multiline_entry_read_only(@entry) > 0 31 | end 32 | 33 | def read_only=(value : Bool) 34 | UI.multiline_entry_set_read_only(@entry, value) 35 | end 36 | 37 | @on_changed_cb : Proc(Nil)? 38 | private def set_on_changed(&cb : ->) 39 | @on_changed_cb = cb 40 | UI.multiline_entry_on_changed @entry, ->(window, data) { 41 | data.as(typeof(cb)*).value.call 42 | }, pointerof(@on_changed_cb) 43 | end 44 | end 45 | 46 | class NonWrappingMultilineEntry < MultilineEntry 47 | def self.new 48 | entry = UI.new_non_wrapping_multlilin_entry 49 | new(entry) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/cru/progress_bar.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class ProgressBar < Control 5 | def initialize(initial_value = 0) 6 | @progress_bar = UI.new_progress_bar 7 | super(@progress_bar) 8 | self.value = initial_value 9 | end 10 | 11 | def value=(value : Int32) 12 | UI.progress_bar_set_value(@progress_bar, value) 13 | end 14 | 15 | def value 16 | UI.progress_bar_value(@progress_bar) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/cru/radio_buttons.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class RadioButtons < Control 5 | Cute.signal selected(radio_buttons : RadioButtons) 6 | 7 | def initialize(items = [] of String) 8 | @radio_buttons = UI.new_radio_buttons 9 | super(@radio_buttons) 10 | items.each { |item| self.append(item) } 11 | set_on_selected { selected.emit(self) } 12 | end 13 | 14 | def append(text : String) 15 | UI.radio_buttons_append(@radio_buttons, text) 16 | end 17 | 18 | def selected_index 19 | UI.radio_buttons_selected(@radio_buttons) 20 | end 21 | 22 | def selected_index=(value : Bool) 23 | UI.radio_buttons_set_selected(@radio_buttons, value) 24 | end 25 | 26 | @on_selected_cb : Proc(Nil)? 27 | private def set_on_selected(&cb : ->) 28 | @on_selected_cb = cb 29 | UI.radio_buttons_on_selected @radio_buttons, ->(window, data) { 30 | data.as(typeof(cb)*).value.call 31 | }, pointerof(@on_selected_cb) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /src/cru/separator.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class HorizontalSeparator < Control 5 | def initialize 6 | @separator = UI.new_horizontal_separator 7 | super(@separator) 8 | end 9 | end 10 | 11 | class VerticalSeparator < Control 12 | def initialize 13 | @separator = UI.new_vertical_separator 14 | super(@separator) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/cru/slider.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Slider < Control 5 | Cute.signal changed(slider : Slider) 6 | 7 | def initialize(min_value, max_value) 8 | @slider = UI.new_slider(min_value, max_value) 9 | super(@slider) 10 | set_on_changed { changed.emit(self); 0 } 11 | end 12 | 13 | def value=(value : Int32) 14 | UI.slider_set_value(@slider, value) 15 | end 16 | 17 | def value 18 | UI.slider_value(@slider) 19 | end 20 | 21 | @on_changed_cb : Proc(Nil)? 22 | private def set_on_changed(&cb : ->) 23 | @on_changed_cb = cb 24 | UI.slider_on_changed @slider, ->(window, data) { 25 | data.as(typeof(cb)*).value.call 26 | }, pointerof(@on_changed_cb) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /src/cru/spinbox.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Spinbox < Control 5 | Cute.signal changed(spinbox : Spinbox) 6 | 7 | def initialize(min_value, max_value) 8 | @spinbox = UI.new_spinbox(min_value, max_value) 9 | super(@spinbox) 10 | set_on_changed { changed.emit(self); 0 } 11 | end 12 | 13 | def value=(value : Int32) 14 | UI.spinbox_set_value(@spinbox, value) 15 | end 16 | 17 | def value 18 | UI.spinbox_value(@spinbox) 19 | end 20 | 21 | @on_changed_cb : Proc(Nil)? 22 | private def set_on_changed(&cb : ->) 23 | @on_changed_cb = cb 24 | UI.spinbox_on_changed @spinbox, ->(window, data) { 25 | data.as(typeof(cb)*).value.call 26 | }, pointerof(@on_changed_cb) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /src/cru/tab.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | class Tab < Control 5 | def initialize 6 | @tab = UI.new_tab 7 | super(@tab) 8 | end 9 | 10 | # Appends a child to the tab. 11 | def append(name : String, control : Control) 12 | UI.tab_append(@tab, name, control) 13 | end 14 | 15 | def insert(name, index, control) 16 | UI.tab_insert_at(@tab, name, index, control) 17 | end 18 | 19 | # Delete a child from the tab. 20 | def delete(index) 21 | UI.tab_delete(@tab, index) 22 | end 23 | 24 | # Returns true if this tab is margined. 25 | def margined? 26 | UI.tab_margined(@tab) 27 | end 28 | 29 | # Sets whether this tab is margined. 30 | def margined=(value : Bool) 31 | UI.tab_set_margined(@tab, value) 32 | end 33 | 34 | def pages 35 | UI.tab_num_pages(@tab) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /src/cru/types/draw_brush.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | class DrawBrush 3 | def initialize(color : Int32, alpha : Float64 = 1.0) 4 | @brush = UI::DrawBrush.new 5 | set_solid_brush(color, alpha) 6 | end 7 | 8 | def color=(color : Int32) 9 | set_solid_brush(color, self.a) 10 | end 11 | 12 | def type 13 | @brush.type 14 | end 15 | 16 | def type=(value : UI::DrawBrushType) 17 | @brush.type = value 18 | end 19 | 20 | def r 21 | @brush.r 22 | end 23 | 24 | def r=(value : Float64) 25 | @brush.r = value 26 | end 27 | 28 | def g 29 | @brush.g 30 | end 31 | 32 | def g=(value : Float64) 33 | @brush.g = value 34 | end 35 | 36 | def b 37 | @brush.b 38 | end 39 | 40 | def b=(value : Float64) 41 | @brush.b = value 42 | end 43 | 44 | def a 45 | @brush.a 46 | end 47 | 48 | def a=(value : Float64) 49 | @brush.a = value 50 | end 51 | 52 | def x0 53 | @brush.x0 54 | end 55 | 56 | def x0=(value : Float64) 57 | @brush.x0 = value 58 | end 59 | 60 | def x1 61 | @brush.x1 62 | end 63 | 64 | def x1=(value : Float64) 65 | @brush.x1 = value 66 | end 67 | 68 | def y0 69 | @brush.y0 70 | end 71 | 72 | def y0=(value : Float64) 73 | @brush.y0 = value 74 | end 75 | 76 | def y1 77 | @brush.y1 78 | end 79 | 80 | def y1=(value : Float64) 81 | @brush.y1 = value 82 | end 83 | 84 | def outer_radius 85 | @brush.outer_radius 86 | end 87 | 88 | def outer_radius=(value : Float64) 89 | @brush.outer_radius = value 90 | end 91 | 92 | def stops 93 | @brush.stops.value 94 | end 95 | 96 | def stops=(value : UI::DrawBrushGradientStop) 97 | @brush.stops = pointerof(value) 98 | end 99 | 100 | def num_stops 101 | @brush.num_stops 102 | end 103 | 104 | def num_stops=(value : Int32) 105 | @brush.num_stops = value 106 | end 107 | 108 | def to_unsafe 109 | pointerof(@brush) 110 | end 111 | 112 | # :nodoc: 113 | private def set_solid_brush(color, alpha) 114 | component = 0_u8 115 | @brush.type = UI::DrawBrushType::Solid 116 | component = ((color >> 16) & 0xFF).to_u8 117 | @brush.r = component.to_f64 / 255 118 | component = ((color >> 8) & 0xFF).to_u8 119 | @brush.g = component.to_f64 / 255 120 | component = (color & 0xFF).to_u8 121 | @brush.b = component.to_f64 / 255 122 | @brush.a = alpha 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /src/cru/types/draw_matrix.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | class DrawMatrix 3 | def initialize 4 | @matrix = uninitialized UI::DrawMatrix 5 | UI.draw_matrix_set_identity(pointerof(@matrix)) 6 | end 7 | 8 | def translate(x, y) 9 | UI.draw_matrix_translate(self, x.to_f, y.to_f) 10 | end 11 | 12 | def scale(x_center, y_center, x, y) 13 | UI.draw_matrix_scale(self, x_center, y_center, x, y) 14 | end 15 | 16 | def rotate(x, y, amount) 17 | UI.draw_matrix_rotate(self, x.to_f, y.to_f, amount.to_f) 18 | end 19 | 20 | def skew(x, y, x_amount, y_amount) 21 | UI.draw_matrix_skew(self, x.to_f, y.to_f, x_amount.to_f, y_amount.to_f) 22 | end 23 | 24 | def multiply(other : DrawMatrix) 25 | UI.draw_matrix_multiply(self, other) 26 | end 27 | 28 | def invertible? 29 | UI.draw_matrix_invertible(self) > 0 30 | end 31 | 32 | def invert 33 | UI.draw_matrix_invert(self) > 0 34 | end 35 | 36 | def transform_point(x, y) 37 | UI.draw_matrix_transform_point(self, x.to_f, y.to_f) 38 | end 39 | 40 | def transform_size(x, y) 41 | UI.draw_matrix_transform_size(self, x.to_f, y.to_f) 42 | end 43 | 44 | def transform(context : UI::DrawContext*) 45 | UI.draw_transform(context, self) 46 | end 47 | 48 | def to_unsafe 49 | pointerof(@matrix) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/cru/types/draw_path.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | class DrawPath 3 | getter fill_mode : UI::DrawFillMode 4 | 5 | def initialize(@fill_mode : UI::DrawFillMode) 6 | @path = UI.new_draw_path(fill_mode) 7 | end 8 | 9 | def new_figure(x : Float64, y : Float64) 10 | UI.draw_path_new_figure(self, x, y) 11 | end 12 | 13 | def new_figure(xCenter : Float64, 14 | yCenter : Float64, 15 | radius : Float64, 16 | startAngle : Float64, 17 | sweep : Float64, 18 | negative : LibC::Int) 19 | UI.draw_path_new_figure_with_arc(self, xCenter, yCenter, radius, startAngle, sweep, negative) 20 | end 21 | 22 | def line_to(x : Float64, y : Float64) 23 | UI.draw_path_line_to(self, x, y) 24 | end 25 | 26 | def arc_to(xCenter : Float64, 27 | yCenter : Float64, 28 | radius : Float64, 29 | startAngle : Float64, 30 | sweep : Float64, 31 | negative : LibC::Int) 32 | UI.draw_path_arc_to(self, xCenter, yCenter, radius, startAngle, sweep, negative) 33 | end 34 | 35 | def bezier_to(c1x : Float64, 36 | c1y : Float64, 37 | c2x : Float64, 38 | c2y : Float64, 39 | endX : Float64, 40 | endY : Float64) 41 | UI.draw_path_bezier_to(c1x, c1y, c2x, c2y, endX, endY) 42 | end 43 | 44 | def close_figure 45 | UI.draw_path_close_figure(self) 46 | end 47 | 48 | def add_rectangle(x : Float64, y : Float64, width : Float64, height : Float64) 49 | UI.draw_path_add_rectangle(self, x, y, width, height) 50 | end 51 | 52 | def end 53 | UI.draw_path_end(self) 54 | end 55 | 56 | def stroke(context : UI::DrawContext*, 57 | brush : DrawBrush, 58 | cap : UI::DrawLineCap = :flat, 59 | join : UI::DrawLineJoin = :miter, 60 | thickness : Float64 = 1.0, 61 | miter_limit : Float64 = UI::DrawDefaultMiterLimit, 62 | dashes : Float64 = 0.0, 63 | num_dashes : Int32 = 0, 64 | dash_phase : Float64 = 1.0) 65 | params = UI::DrawStrokeParams.new(cap: cap, join: join, thickness: thickness, miter_limit: miter_limit, dashes: dashes, num_dashes: num_dashes, dash_phase: dash_phase) 66 | UI.draw_path_stroke(context, self, brush, pointerof(params)) 67 | end 68 | 69 | def fill(context : UI::DrawContext*, brush : DrawBrush) 70 | UI.draw_path_fill(context, self, brush) 71 | end 72 | 73 | def free 74 | UI.draw_free_path(@path) 75 | end 76 | 77 | def to_unsafe 78 | @path 79 | end 80 | 81 | def finalize 82 | self.free 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /src/cru/types/font.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | record Font, family : String, 3 | size : Float64, 4 | weight : UI::TextWeight, 5 | italic : UI::TextItalic, 6 | stretch : UI::TextStretch do 7 | def self.from_descriptor(d : UI::FontDescriptor) 8 | font = new(String.new(d.family), 9 | d.weight.to_f64, 10 | d.weight, 11 | d.italic, 12 | d.stretch) 13 | UI.free_font_button_font(pointerof(d)) 14 | font 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/cru/version.cr: -------------------------------------------------------------------------------- 1 | module Cru 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /src/cru/window.cr: -------------------------------------------------------------------------------- 1 | require "./control" 2 | 3 | module Cru 4 | # Represents a Window 5 | class Window < Control 6 | Cute.signal closing(window : Window) 7 | Cute.signal content_size_changed(window : Window) 8 | 9 | def initialize(@window : UI::Window*) 10 | super(@window) 11 | end 12 | 13 | def initialize(title : String, 14 | width = 800, 15 | height = 600, 16 | menu_bar = true) 17 | @window = UI.new_window(title, width, height, menu_bar ? 1 : 0) 18 | super(@window) 19 | 20 | set_on_closing { closing.emit(self); 0 } 21 | set_on_content_size_changed { content_size_changed.emit(self) } 22 | end 23 | 24 | # Get the window's title. 25 | def title 26 | String.new(UI.window_title(@window)) 27 | end 28 | 29 | # Set the window's title. 30 | def title=(value : String) 31 | UI.window_set_title(@window, value) 32 | end 33 | 34 | # Get the window's width. 35 | def width 36 | UI.window_content_size(@window, out width, out height) 37 | width 38 | end 39 | 40 | # Get the window's height. 41 | def height 42 | UI.window_content_size(@window, out width, out height) 43 | height 44 | end 45 | 46 | # Set the window's width. 47 | def width=(value : Int32) 48 | UI.window_set_content_size(@window, width, self.height) 49 | end 50 | 51 | # Set the window's height. 52 | def height=(value : Int32) 53 | UI.window_set_content_size(@window, self.width, height) 54 | end 55 | 56 | # Returns true if this window is fullscreen 57 | def fullscreen? 58 | UI.window_full_screen(@window) > 0 59 | end 60 | 61 | # Set whether the window is fullscreen. 62 | def fullscreen=(value : Bool) 63 | UI.window_set_fullscreen(@window, value) 64 | end 65 | 66 | # Returns true if this window is borderless. 67 | def borderless? 68 | UI.window_borderless(@window) > 0 69 | end 70 | 71 | # Set whether this window is borderless. 72 | def borderless=(value : Bool) 73 | UI.window_set_borderless(@window, value) 74 | end 75 | 76 | # Adds a control as a child of this window. 77 | def child=(child : Control) 78 | UI.window_set_child(@window, child) 79 | end 80 | 81 | # Returns true if this window is margined. 82 | def margined? 83 | UI.window_margined(@window) > 0 84 | end 85 | 86 | # Set whether this window is margined. 87 | def margined=(value : Bool) 88 | UI.window_set_margined(@window, value) 89 | end 90 | 91 | # Opens an "open file" dialog. 92 | def open_file 93 | res = UI.open_file(@window) 94 | ui_nil?(res) ? nil : String.new(res) 95 | end 96 | 97 | # Opens a "save file" dialog. 98 | def save_file 99 | res = UI.save_file(@window) 100 | ui_nil?(res) ? nil : String.new(res) 101 | end 102 | 103 | # Opens a new message dialog (like an alert). 104 | def show_message(title : String, description : String) 105 | UI.msg_box(@window, title, description) 106 | end 107 | 108 | # Opens a new error dialog. 109 | def show_error(title : String, description : String) 110 | UI.msg_box_error(@window, title, description) 111 | end 112 | 113 | @on_closing_cb : Proc(Nil)? 114 | private def set_on_closing(&cb : -> Int32) 115 | @on_closing_cb = cb 116 | UI.window_on_closing @window, ->(window, data) { 117 | data.as(typeof(cb)*).value.call 118 | }, pointerof(@on_closing_cb) 119 | end 120 | 121 | @on_content_size_changed_cb : Proc(Nil)? 122 | private def set_on_content_size_changed(&cb : ->) 123 | @on_content_size_changed_cb = cb 124 | UI.window_on_content_size_changed @window, ->(window, data) { 125 | data.as(typeof(cb)*).value.call 126 | }, pointerof(@on_content_size_changed_cb) 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /src/ext/time.cr: -------------------------------------------------------------------------------- 1 | struct Time 2 | def self.from_tm_ptr(tm : LibC::Tm*) 3 | secs = LibC.mktime(tm) 4 | Time.unix(secs) 5 | end 6 | 7 | def to_tm_ptr 8 | secs = self.to_unix 9 | LibC.localtime_r(pointerof(secs), out tm) 10 | pointerof(tm) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /static/control_gallery_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watzon/cru/79b65e509b878b4dabd5c3b4a7520891fe706af0/static/control_gallery_screenshot.png --------------------------------------------------------------------------------