├── .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 |

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
--------------------------------------------------------------------------------