glimmer-dsl-libui 0.12.1 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +2 -2
- data/VERSION +1 -1
- data/examples/basic_code_area.rb +0 -3
- data/examples/basic_code_entry.rb +30 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/libui/control_proxy/tab_item_proxy.rb +12 -4
- data/lib/glimmer/libui/control_proxy/text_proxy.rb +12 -12
- data/lib/glimmer/libui/control_proxy.rb +3 -0
- data/lib/glimmer/libui/custom_control/code_area.rb +23 -44
- data/lib/glimmer/libui/custom_control/code_entry.rb +300 -0
- data/lib/glimmer/libui/data_bindable.rb +1 -1
- data/lib/glimmer/libui/syntax_highlighter.rb +47 -0
- data/lib/glimmer/libui.rb +7 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 467e3450aca13a338d5d835aa79c7b483cd9475866d6e6f08e2b130386107d1a
|
4
|
+
data.tar.gz: 81674d7b0b95d973b5286c1c8cc187e747412e02a6a0f7bffc7d235b499186c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ada3fc2a2db26d4660876010f1b8f0cdc08797baa0b0e63e5a044e050dc9cf400f6318dc7e09328137b755c861695c1a74901ec91be2add91433c1d487ba1438
|
7
|
+
data.tar.gz: f1aee5106ee4500c46970de47136da231c4e096a213f65095cb2640e8b978672100ef036ebb8a24fbd65137ec222f84a345832c6fe69bc87066599496658c8b6
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.12.2
|
4
|
+
|
5
|
+
- New `code_entry` control [ALPHA/EXPERIMENTAL/INCOMPLETE/UNSTABLE] (similar to `code_area`, but with code editing functionality).
|
6
|
+
- New examples/basic_code_entry.rb
|
7
|
+
- Update examples/basic_code_area.rb to have padding inside the `code_area` and no window padding anymore
|
8
|
+
- Have `code_area` handle italic syntax highlighting styles
|
9
|
+
- Support `code_area` `font_family` and `font_size` options
|
10
|
+
- Controls prepend `Parent` mixin to keep track of their children via `children` attribute
|
11
|
+
- tab_item always generates a vertical_box root container (root_proxy) before adding its child content within it (also, it does not generate a horizontal_box anymore by default if it had no content to start, yet a vertical_box all the time)
|
12
|
+
- Upgrade to glimmer 2.7.9
|
13
|
+
- Fix issue in `code_area` not recognizing indentation
|
14
|
+
|
3
15
|
## 0.12.1
|
4
16
|
|
5
17
|
- Fix issue with getting error "superclass mismatch for class Array" when `concurrent-ruby` is included in a desktop applications as it is required to load `concurrent-ruby` before `glimmer`, so now `glimmer-dsl-libui` will attempt loading `concurrent-ruby` if available or default to old behavior otherwise.
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.12.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.12.2
|
2
2
|
## Prerequisite-Free Ruby Desktop Development Cross-Platform Native GUI Library ([Fukuoka Award Winning](http://www.digitalfukuoka.jp/topics/187?locale=ja))
|
3
3
|
### The Quickest Way From Zero To GUI
|
4
4
|
[](http://badge.fury.io/rb/glimmer-dsl-libui)
|
@@ -456,7 +456,7 @@ gem install glimmer-dsl-libui
|
|
456
456
|
Or install via Bundler `Gemfile`:
|
457
457
|
|
458
458
|
```ruby
|
459
|
-
gem 'glimmer-dsl-libui', '~> 0.12.
|
459
|
+
gem 'glimmer-dsl-libui', '~> 0.12.2'
|
460
460
|
```
|
461
461
|
|
462
462
|
Test that installation worked by running the [Glimmer Meta-Example](#examples):
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.12.
|
1
|
+
0.12.2
|
data/examples/basic_code_area.rb
CHANGED
@@ -7,7 +7,6 @@ class BasicCodeArea
|
|
7
7
|
@code = <<~CODE
|
8
8
|
# Greets target with greeting
|
9
9
|
def greet(greeting: 'Hello', target: 'World')
|
10
|
-
|
11
10
|
puts "\#{greeting}, \#{target}!"
|
12
11
|
end
|
13
12
|
|
@@ -21,8 +20,6 @@ class BasicCodeArea
|
|
21
20
|
|
22
21
|
body {
|
23
22
|
window('Basic Code Area', 400, 300) {
|
24
|
-
margined true
|
25
|
-
|
26
23
|
code_area(language: 'ruby', code: @code)
|
27
24
|
}
|
28
25
|
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'glimmer-dsl-libui'
|
2
|
+
|
3
|
+
# code_entry is experimental/incomplete and not recommended for serious usage
|
4
|
+
class BasicCodeEntry
|
5
|
+
include Glimmer::LibUI::Application
|
6
|
+
|
7
|
+
before_body do
|
8
|
+
@code = <<~CODE
|
9
|
+
# Greets target with greeting
|
10
|
+
def greet(greeting: 'Hello', target: 'World')
|
11
|
+
puts "\#{greeting}, \#{target}!"
|
12
|
+
end
|
13
|
+
|
14
|
+
greet
|
15
|
+
greet(target: 'Robert')
|
16
|
+
greet(greeting: 'Aloha')
|
17
|
+
greet(greeting: 'Aloha', target: 'Nancy')
|
18
|
+
greet(greeting: 'Howdy', target: 'Doodle')
|
19
|
+
CODE
|
20
|
+
end
|
21
|
+
|
22
|
+
body {
|
23
|
+
window('Basic Code Entry', 400, 300) {
|
24
|
+
# code_entry is experimental/incomplete and not recommended for serious usage
|
25
|
+
code_entry(language: 'ruby', code: @code)
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
BasicCodeEntry.launch
|
data/glimmer-dsl-libui.gemspec
CHANGED
Binary file
|
@@ -29,17 +29,18 @@ module Glimmer
|
|
29
29
|
#
|
30
30
|
# Follows the Proxy Design Pattern
|
31
31
|
class TabItemProxy < ControlProxy
|
32
|
-
attr_reader :index
|
32
|
+
attr_reader :index, :root_proxy
|
33
33
|
|
34
34
|
def initialize(keyword, parent, args, &block)
|
35
35
|
@keyword = keyword
|
36
36
|
@parent_proxy = parent
|
37
|
+
@root_proxy = nil
|
37
38
|
@args = args
|
38
39
|
@block = block
|
39
40
|
@enabled = 1
|
40
41
|
@index = @parent_proxy.num_pages
|
41
|
-
@content = @block&.call
|
42
42
|
build_control
|
43
|
+
@content = @root_proxy.content(&@block) if @block
|
43
44
|
end
|
44
45
|
|
45
46
|
def name
|
@@ -57,11 +58,18 @@ module Glimmer
|
|
57
58
|
alias margined= margined
|
58
59
|
alias margined? margined
|
59
60
|
|
61
|
+
# TODO implement tab_insert_at
|
62
|
+
|
63
|
+
def destroy
|
64
|
+
@parent_proxy.delete(@index)
|
65
|
+
end
|
66
|
+
|
60
67
|
private
|
61
68
|
|
62
69
|
def build_control
|
63
|
-
@
|
64
|
-
@
|
70
|
+
@root_proxy = Box::VerticalBoxProxy.new('vertical_box', @parent_proxy, [])
|
71
|
+
# TODO support being able to re-open a tab item and add content.. it needs special code.. perhaps by having a @content parent for every tab item, this problem becomes easier to solve
|
72
|
+
@parent_proxy.append(name, @root_proxy.libui) # returns nil, so there is no @libui object to store in this case
|
65
73
|
end
|
66
74
|
end
|
67
75
|
end
|
@@ -203,7 +203,7 @@ module Glimmer
|
|
203
203
|
def extent_width
|
204
204
|
if @extent_width.to_f > 0
|
205
205
|
@extent_width
|
206
|
-
else
|
206
|
+
else # TODO auto calculate extents
|
207
207
|
width
|
208
208
|
end
|
209
209
|
end
|
@@ -211,7 +211,7 @@ module Glimmer
|
|
211
211
|
def extent_height
|
212
212
|
if @extent_height.to_f > 0
|
213
213
|
@extent_height
|
214
|
-
else
|
214
|
+
else # TODO auto calculate extents
|
215
215
|
children_max_size = children.map(&:font).map {|font| font[:size] if font.respond_to?(:[]) }.compact.max
|
216
216
|
if children_max_size.to_f > 0
|
217
217
|
children_max_size
|
@@ -221,6 +221,16 @@ module Glimmer
|
|
221
221
|
end
|
222
222
|
end
|
223
223
|
|
224
|
+
def calculate_extents
|
225
|
+
# TODO fix implementation once libui binding project responds about this
|
226
|
+
# as it always returns 0,0 right now
|
227
|
+
extent_width = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE*8)
|
228
|
+
extent_height = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE*8)
|
229
|
+
::LibUI.draw_text_layout_extents(@libui, extent_width, extent_height)
|
230
|
+
@extent_width = extent_width[0, Fiddle::SIZEOF_DOUBLE*8].unpack1('i')
|
231
|
+
@extent_height = extent_height[0, Fiddle::SIZEOF_DOUBLE*8].unpack1('i')
|
232
|
+
end
|
233
|
+
|
224
234
|
private
|
225
235
|
|
226
236
|
def build_control
|
@@ -234,16 +244,6 @@ module Glimmer
|
|
234
244
|
draw_brush.B = (draw_brush_args[:b] || draw_brush_args[:blue]).to_f / 255.0
|
235
245
|
draw_brush.A = (draw_brush_args[:a] || draw_brush_args[:alpha])
|
236
246
|
end
|
237
|
-
|
238
|
-
def calculate_extents
|
239
|
-
# TODO fix implementation once libui binding project responds about this
|
240
|
-
# as it always returns 0,0 right now
|
241
|
-
extent_width = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE*8)
|
242
|
-
extent_height = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE*8)
|
243
|
-
::LibUI.draw_text_layout_extents(@libui, extent_width, extent_height)
|
244
|
-
@extent_width = extent_width[0, Fiddle::SIZEOF_DOUBLE*8].unpack1('i')
|
245
|
-
@extent_height = extent_height[0, Fiddle::SIZEOF_DOUBLE*8].unpack1('i')
|
246
|
-
end
|
247
247
|
end
|
248
248
|
end
|
249
249
|
end
|
@@ -20,6 +20,7 @@
|
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
22
|
require 'glimmer/libui/data_bindable'
|
23
|
+
require 'glimmer/libui/parent'
|
23
24
|
|
24
25
|
module Glimmer
|
25
26
|
module LibUI
|
@@ -104,6 +105,7 @@ module Glimmer
|
|
104
105
|
end
|
105
106
|
|
106
107
|
include DataBindable
|
108
|
+
prepend Parent
|
107
109
|
|
108
110
|
KEYWORD_ALIASES = {
|
109
111
|
'message_box' => 'msg_box',
|
@@ -354,6 +356,7 @@ module Glimmer
|
|
354
356
|
deregister_all_custom_listeners
|
355
357
|
send_to_libui('destroy')
|
356
358
|
ControlProxy.control_proxies.delete(self)
|
359
|
+
# TODO should we destroy all children too or at least remove them from the children collection?
|
357
360
|
end
|
358
361
|
|
359
362
|
def enabled(value = nil)
|
@@ -19,24 +19,15 @@
|
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
|
+
require 'os'
|
23
|
+
|
22
24
|
require 'glimmer/libui/custom_control'
|
25
|
+
require 'glimmer/libui/syntax_highlighter'
|
23
26
|
|
24
27
|
module Glimmer
|
25
28
|
module LibUI
|
26
29
|
module CustomControl
|
27
30
|
class CodeArea
|
28
|
-
class << self
|
29
|
-
def languages
|
30
|
-
require 'rouge'
|
31
|
-
Rouge::Lexer.all.map {|lexer| lexer.tag}.sort
|
32
|
-
end
|
33
|
-
|
34
|
-
def lexers
|
35
|
-
require 'rouge'
|
36
|
-
Rouge::Lexer.all.sort_by(&:title)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
31
|
include Glimmer::LibUI::CustomControl
|
41
32
|
|
42
33
|
REGEX_COLOR_HEX6 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/
|
@@ -44,50 +35,38 @@ module Glimmer
|
|
44
35
|
option :language, default: 'ruby'
|
45
36
|
option :theme, default: 'glimmer'
|
46
37
|
option :code
|
38
|
+
option :padding, default: 10
|
39
|
+
option :font_family, default: OS.mac? ? 'Consolas' : 'Courier'
|
40
|
+
option :font_size, default: 13
|
41
|
+
|
42
|
+
attr_reader :syntax_highlighter
|
43
|
+
|
44
|
+
before_body do
|
45
|
+
@syntax_highlighter = SyntaxHighlighter.new(language: language, theme: theme)
|
46
|
+
@font_default = {family: font_family, size: font_size, weight: :medium, italic: :normal, stretch: :normal}
|
47
|
+
@font_italic = @font_default.merge(italic: :italic)
|
48
|
+
end
|
47
49
|
|
48
50
|
body {
|
49
51
|
area {
|
50
52
|
rectangle(0, 0, 8000, 8000) {
|
51
53
|
fill :white
|
52
54
|
}
|
53
|
-
text {
|
54
|
-
default_font
|
55
|
+
text(padding, padding) {
|
56
|
+
default_font @font_default
|
55
57
|
|
56
|
-
syntax_highlighting(code).each do |token|
|
57
|
-
|
58
|
-
|
59
|
-
string(
|
60
|
-
|
61
|
-
|
58
|
+
syntax_highlighter.syntax_highlighting(code).each do |token|
|
59
|
+
token_text = token[:token_text].start_with?("\n") ? " #{token[:token_text]}" : token[:token_text]
|
60
|
+
|
61
|
+
string(token_text) {
|
62
|
+
font @font_italic if token[:token_style][:italic]
|
63
|
+
color token[:token_style][:fg] || :black
|
64
|
+
background token[:token_style][:bg] || :white
|
62
65
|
}
|
63
66
|
end
|
64
67
|
}
|
65
68
|
}
|
66
69
|
}
|
67
|
-
|
68
|
-
def lexer
|
69
|
-
require 'rouge'
|
70
|
-
require 'glimmer-dsl-libui/ext/rouge/theme/glimmer'
|
71
|
-
# TODO Try to use Rouge::Lexer.find_fancy('guess', code) in the future to guess the language or otherwise detect it from file extension
|
72
|
-
@lexer ||= Rouge::Lexer.find_fancy(language)
|
73
|
-
@lexer ||= Rouge::Lexer.find_fancy('ruby') # default to Ruby if no lexer is found
|
74
|
-
end
|
75
|
-
|
76
|
-
def syntax_highlighting(text)
|
77
|
-
return [] if text.to_s.strip.empty?
|
78
|
-
@syntax_highlighting ||= {}
|
79
|
-
unless @syntax_highlighting.keys.include?(text)
|
80
|
-
lex = lexer.lex(text).to_a
|
81
|
-
text_size = 0
|
82
|
-
@syntax_highlighting[text] = lex.map do |pair|
|
83
|
-
{token_type: pair.first, token_text: pair.last}
|
84
|
-
end.each do |hash|
|
85
|
-
hash[:token_index] = text_size
|
86
|
-
text_size += hash[:token_text].size
|
87
|
-
end
|
88
|
-
end
|
89
|
-
@syntax_highlighting[text]
|
90
|
-
end
|
91
70
|
end
|
92
71
|
end
|
93
72
|
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
# Copyright (c) 2021-2024 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'os'
|
23
|
+
|
24
|
+
require 'glimmer/libui/custom_control'
|
25
|
+
require 'glimmer/libui/syntax_highlighter'
|
26
|
+
|
27
|
+
module Glimmer
|
28
|
+
module LibUI
|
29
|
+
module CustomControl
|
30
|
+
class CodeEntry
|
31
|
+
include Glimmer::LibUI::CustomControl
|
32
|
+
|
33
|
+
REGEX_COLOR_HEX6 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/
|
34
|
+
# TODO vary shortcut key by OS (CMD for Mac, CTRL elsewhere)
|
35
|
+
|
36
|
+
option :language, default: 'ruby'
|
37
|
+
option :theme, default: 'glimmer'
|
38
|
+
option :code
|
39
|
+
option :padding, default: 10
|
40
|
+
option :caret_blinking_delay_in_seconds, default: 0.5
|
41
|
+
option :font_family, default: OS.mac? ? 'Courier New' : 'Courier'
|
42
|
+
option :font_size, default: 14
|
43
|
+
# TODO consider offering the option to autosave to a file upon changes
|
44
|
+
|
45
|
+
attr_reader :syntax_highlighter, :line, :position
|
46
|
+
|
47
|
+
before_body do
|
48
|
+
@syntax_highlighter = SyntaxHighlighter.new(language: language, theme: theme)
|
49
|
+
@font_default = {family: font_family, size: font_size, weight: :medium, italic: :normal, stretch: :normal}
|
50
|
+
@font_italic = @font_default.merge(italic: :italic)
|
51
|
+
@line = 0
|
52
|
+
@position = 5
|
53
|
+
@draw_caret = false
|
54
|
+
@multiplier_position = 0.6
|
55
|
+
@multiplier_line = 1.2
|
56
|
+
end
|
57
|
+
|
58
|
+
after_body do
|
59
|
+
LibUI.timer(caret_blinking_delay_in_seconds/2.0) do
|
60
|
+
body_root.redraw
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
body {
|
65
|
+
scrolling_area(1, 1) { |code_entry_area|
|
66
|
+
on_draw do
|
67
|
+
# TODO need to determine the scrolling area width and height from the text extent once supported in the future
|
68
|
+
# TODO only reset size when a new line has been added
|
69
|
+
area_width = longest_line_size * font_size*@multiplier_position
|
70
|
+
area_height = line_count * font_size*@multiplier_line
|
71
|
+
code_entry_area.set_size(area_width, area_height)
|
72
|
+
|
73
|
+
rectangle(0, 0, area_width, area_height) {
|
74
|
+
fill :white
|
75
|
+
}
|
76
|
+
|
77
|
+
code_layer
|
78
|
+
|
79
|
+
caret_layer if @draw_caret
|
80
|
+
|
81
|
+
if @blinking_time.nil? || (Time.now - @blinking_time > caret_blinking_delay_in_seconds)
|
82
|
+
@blinking_time = Time.now
|
83
|
+
@draw_caret = !@draw_caret
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
on_mouse_down do |mouse_event|
|
88
|
+
# once text extent calculation via libui is supported, consider the idea of splitting
|
89
|
+
# text by single characters to use every character extent in determining mouse location
|
90
|
+
# or not splitting but using the extent of one character to determine mouse location
|
91
|
+
@position = (mouse_event[:x] - padding) / (font_size*@multiplier_position)
|
92
|
+
@line = (mouse_event[:y] - padding) / (font_size*@multiplier_line)
|
93
|
+
@line = [@line, code.lines.length - 1].min
|
94
|
+
@position = [@position, current_code_line_max_position].min
|
95
|
+
body_root.redraw
|
96
|
+
end
|
97
|
+
# TODO mouse click based text selection
|
98
|
+
# TODO keyboar based text selection
|
99
|
+
|
100
|
+
on_key_down do |key_event|
|
101
|
+
# TODO consider delegating some of the logic below to a model
|
102
|
+
handled = true # assume it is handled for all cases except the else clause below
|
103
|
+
case key_event
|
104
|
+
in modifiers: [], ext_key: :left
|
105
|
+
if @position == 0
|
106
|
+
if @line > 0
|
107
|
+
new_position = code.lines[line - 1].length - 1
|
108
|
+
@line = [@line - 1, 0].max
|
109
|
+
@position = new_position
|
110
|
+
end
|
111
|
+
else
|
112
|
+
@position = [@position - 1, 0].max
|
113
|
+
end
|
114
|
+
in modifiers: [], ext_key: :right
|
115
|
+
if @position == current_code_line_max_position
|
116
|
+
if @line < code.lines.size - 1
|
117
|
+
@line += 1
|
118
|
+
@position = 0
|
119
|
+
end
|
120
|
+
else
|
121
|
+
@position += 1
|
122
|
+
end
|
123
|
+
in modifiers: [], ext_key: :up
|
124
|
+
# TODO scroll view when going down or up or paging or going home / end
|
125
|
+
@line = [@line - 1, 0].max
|
126
|
+
if @max_position
|
127
|
+
@position = @max_position
|
128
|
+
@max_position = nil
|
129
|
+
end
|
130
|
+
in modifiers: [], ext_key: :down
|
131
|
+
@line += 1
|
132
|
+
if @max_position
|
133
|
+
@position = @max_position
|
134
|
+
@max_position = nil
|
135
|
+
end
|
136
|
+
in modifiers: [], ext_key: :page_up
|
137
|
+
@line = [@line - 15, 0].max
|
138
|
+
if @max_position
|
139
|
+
@position = @max_position
|
140
|
+
@max_position = nil
|
141
|
+
end
|
142
|
+
in modifiers: [], ext_key: :page_down
|
143
|
+
@line += 15
|
144
|
+
if @max_position
|
145
|
+
@position = @max_position
|
146
|
+
@max_position = nil
|
147
|
+
end
|
148
|
+
in modifiers: [], ext_key: :home
|
149
|
+
@line = 0
|
150
|
+
@position = 0
|
151
|
+
in modifiers: [], ext_key: :end
|
152
|
+
@line = code.lines.size - 1
|
153
|
+
@position = current_code_line_max_position
|
154
|
+
in ext_key: :delete
|
155
|
+
code.slice!(caret_index)
|
156
|
+
in key: "\n"
|
157
|
+
code.insert(caret_index, "\n")
|
158
|
+
@line += 1
|
159
|
+
@position = 0
|
160
|
+
# TODO indent upon hitting enter
|
161
|
+
in key: "\b"
|
162
|
+
if @position == 0
|
163
|
+
if @line > 0
|
164
|
+
new_position = code.lines[line - 1].length - 1
|
165
|
+
code.slice!(caret_index - 1)
|
166
|
+
@line = [@line - 1, 0].max
|
167
|
+
@position = new_position
|
168
|
+
end
|
169
|
+
else
|
170
|
+
@position = [@position - 1, 0].max
|
171
|
+
code.slice!(caret_index)
|
172
|
+
end
|
173
|
+
in key: "\t"
|
174
|
+
code.insert(caret_index, ' ')
|
175
|
+
@position += 2
|
176
|
+
in modifiers: [:control], key: 'a'
|
177
|
+
@position = 0
|
178
|
+
in modifiers: [:command], ext_key: :left
|
179
|
+
@position = 0
|
180
|
+
in modifiers: [:control], key: 'e'
|
181
|
+
@position = current_code_line_max_position
|
182
|
+
in modifiers: [:command], ext_key: :right
|
183
|
+
@position = current_code_line_max_position
|
184
|
+
in modifiers: [:shift], key_code: 48
|
185
|
+
code.insert(caret_index, ')')
|
186
|
+
@position += 1
|
187
|
+
in modifiers: [:alt], ext_key: :right
|
188
|
+
if @position == current_code_line_max_position
|
189
|
+
if @line < code.lines.size - 1
|
190
|
+
@line += 1
|
191
|
+
@position = 0
|
192
|
+
end
|
193
|
+
else
|
194
|
+
new_caret_index = caret_index
|
195
|
+
new_caret_index += 1 while code[new_caret_index + 1]&.match(/[^a-zA-Z]/)
|
196
|
+
new_caret_index += 1 until code[new_caret_index + 1].nil? || code[new_caret_index + 1].match(/[^a-zA-Z]/)
|
197
|
+
@position += new_caret_index + 1 - caret_index
|
198
|
+
end
|
199
|
+
in modifiers: [:alt], ext_key: :left
|
200
|
+
if @position == 0
|
201
|
+
if @line > 0
|
202
|
+
new_position = code.lines[line - 1].length - 1
|
203
|
+
@line = [@line - 1, 0].max
|
204
|
+
@position = new_position
|
205
|
+
end
|
206
|
+
else
|
207
|
+
new_caret_index = caret_index
|
208
|
+
new_caret_index -= 1 while code[new_caret_index - 1]&.match(/[^a-zA-Z]/)
|
209
|
+
new_caret_index -= 1 until code[new_caret_index + 1].nil? || code[new_caret_index - 1].match(/[^a-zA-Z]/)
|
210
|
+
@position -= caret_index - new_caret_index
|
211
|
+
@position = [@position, 0].max
|
212
|
+
end
|
213
|
+
in modifier: nil, modifiers: []
|
214
|
+
code.insert(caret_index, key_event[:key])
|
215
|
+
@position += 1
|
216
|
+
in modifier: nil, modifiers: [:shift]
|
217
|
+
character = key_event[:key] || key_event[:key_code].chr.capitalize
|
218
|
+
code.insert(caret_index, character)
|
219
|
+
@position += 1
|
220
|
+
# TODO CMD Z (undo)
|
221
|
+
# TODO CMD SHIFT Z (redo)
|
222
|
+
# TODO CMD + [ (outdent)
|
223
|
+
# TODO CMD + ] (indent)
|
224
|
+
# TODO CMD + down (move line down)
|
225
|
+
# TODO CMD + up (move line up)
|
226
|
+
# TODO CMD + D (duplicate)
|
227
|
+
else
|
228
|
+
handled = false
|
229
|
+
end
|
230
|
+
@line = [@line, code.lines.length - 1].min
|
231
|
+
@line = [@line, 0].max
|
232
|
+
new_position = [@position, current_code_line_max_position].min
|
233
|
+
if new_position != @position
|
234
|
+
@max_position = @position
|
235
|
+
@position = new_position
|
236
|
+
end
|
237
|
+
@draw_caret = true
|
238
|
+
body_root.redraw
|
239
|
+
handled
|
240
|
+
end
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
def code_layer
|
245
|
+
text(padding, padding) {
|
246
|
+
default_font @font_default
|
247
|
+
|
248
|
+
syntax_highlighter.syntax_highlighting(code).each do |token|
|
249
|
+
token_text = token[:token_text].start_with?("\n") ? " #{token[:token_text]}" : token[:token_text]
|
250
|
+
|
251
|
+
string(token_text) {
|
252
|
+
font @font_italic if token[:token_style][:italic]
|
253
|
+
color token[:token_style][:fg] || :black
|
254
|
+
background token[:token_style][:bg] || :white
|
255
|
+
}
|
256
|
+
end
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
def caret_layer
|
261
|
+
# TODO adjust padding offset based on text extent
|
262
|
+
text(padding - 4, padding) {
|
263
|
+
default_font @font_default
|
264
|
+
|
265
|
+
# TODO make caret blink
|
266
|
+
string(caret_text) {
|
267
|
+
color :black
|
268
|
+
background [0, 0, 0, 0]
|
269
|
+
}
|
270
|
+
}
|
271
|
+
end
|
272
|
+
|
273
|
+
def caret_text
|
274
|
+
# TODO replace | with a real caret (see if there is an emoji or special character for it)
|
275
|
+
("\n" * @line) + (' ' * @position) + '|'
|
276
|
+
end
|
277
|
+
|
278
|
+
def caret_index
|
279
|
+
code.lines[0..line].join.length - (current_code_line.length - @position)
|
280
|
+
end
|
281
|
+
|
282
|
+
def current_code_line
|
283
|
+
code.lines[@line]
|
284
|
+
end
|
285
|
+
|
286
|
+
def current_code_line_max_position
|
287
|
+
(current_code_line && current_code_line.length > 0) ? (current_code_line.length - 1) : 0
|
288
|
+
end
|
289
|
+
|
290
|
+
def longest_line_size
|
291
|
+
code&.lines&.map(&:size)&.max || 1
|
292
|
+
end
|
293
|
+
|
294
|
+
def line_count
|
295
|
+
code&.lines&.size || 1
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -43,7 +43,7 @@ module Glimmer
|
|
43
43
|
# and updates view property accordingly
|
44
44
|
def data_bind_read(property, model_binding)
|
45
45
|
model_attribute_observer = Glimmer::DataBinding::Observer.proc do
|
46
|
-
new_value = model_binding.evaluate_property
|
46
|
+
new_value = model_binding.evaluate_property # TODO performance might be worse for not using block value instead (double double call on evaluate_property)
|
47
47
|
send("#{property}=", new_value) unless send(property) == new_value
|
48
48
|
end
|
49
49
|
observer_registration = model_attribute_observer.observe(model_binding, attribute_writer_type: [:attribute=, :set_attribute])
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class SyntaxHighlighter
|
2
|
+
class << self
|
3
|
+
def languages
|
4
|
+
require 'rouge'
|
5
|
+
Rouge::Lexer.all.map {|lexer| lexer.tag}.sort
|
6
|
+
end
|
7
|
+
|
8
|
+
def lexers
|
9
|
+
require 'rouge'
|
10
|
+
Rouge::Lexer.all.sort_by(&:title)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :language, :theme
|
15
|
+
|
16
|
+
def initialize(language:, theme:)
|
17
|
+
@language = language
|
18
|
+
@theme = theme
|
19
|
+
end
|
20
|
+
|
21
|
+
def lexer
|
22
|
+
require 'rouge'
|
23
|
+
require 'glimmer-dsl-libui/ext/rouge/theme/glimmer'
|
24
|
+
# TODO Try to use Rouge::Lexer.find_fancy('guess', code) in the future to guess the language or otherwise detect it from file extension
|
25
|
+
@lexer ||= Rouge::Lexer.find_fancy(language)
|
26
|
+
@lexer ||= Rouge::Lexer.find_fancy('ruby') # default to Ruby if no lexer is found
|
27
|
+
end
|
28
|
+
|
29
|
+
def syntax_highlighting(text)
|
30
|
+
return [] if text.to_s.strip.empty?
|
31
|
+
@syntax_highlighting ||= {} # TODO consider memoizing/remembering the last value only to avoid wasting memory
|
32
|
+
unless @syntax_highlighting.keys.include?(text)
|
33
|
+
lex = lexer.lex(text).to_a
|
34
|
+
text_size = 0
|
35
|
+
@syntax_highlighting[text] = lex.map do |pair|
|
36
|
+
token_type = pair.first
|
37
|
+
token_text = pair.last
|
38
|
+
token_style = Rouge::Theme.find(theme).new.style_for(token_type)
|
39
|
+
{token_type: token_type, token_text: token_text, token_style: token_style}
|
40
|
+
end.each do |hash|
|
41
|
+
hash[:token_index] = text_size # TODO do we really need this? Also, consider renaming to something like token_character_index to clarify it is not the index of the token itself yet its first character
|
42
|
+
text_size += hash[:token_text].size
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@syntax_highlighting[text]
|
46
|
+
end
|
47
|
+
end
|
data/lib/glimmer/libui.rb
CHANGED
@@ -199,6 +199,13 @@ module Glimmer
|
|
199
199
|
Color::RGB.constants.reject {|c| c.to_s.upcase == c.to_s}.map(&:to_s).map(&:underscore).map(&:to_sym)
|
200
200
|
end
|
201
201
|
|
202
|
+
# Returns OS shortcut key, meaning the key used with most shorcuts,
|
203
|
+
# like :command on the Mac (used in CMD+S for save)
|
204
|
+
# or :control on Windows and Linux (used in CONTROL+S for save)
|
205
|
+
def os_shortcut_key
|
206
|
+
@os_shortcut_key ||= OS.mac? ? :command : :ctrl
|
207
|
+
end
|
208
|
+
|
202
209
|
# Queues block to execute at the nearest opportunity possible on the main GUI event loop
|
203
210
|
def queue_main(&block)
|
204
211
|
closure = fiddle_closure_block_caller(4, [0]) do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glimmer-dsl-libui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.12.
|
4
|
+
version: 0.12.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: glimmer
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.7.
|
19
|
+
version: 2.7.9
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.7.
|
26
|
+
version: 2.7.9
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: perfect-shape
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.0.
|
61
|
+
version: 1.0.1
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: 2.0.0
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
requirements:
|
69
69
|
- - ">="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: 1.0.
|
71
|
+
version: 1.0.1
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: 2.0.0
|
@@ -388,6 +388,7 @@ files:
|
|
388
388
|
- examples/basic_button.rb
|
389
389
|
- examples/basic_child_window.rb
|
390
390
|
- examples/basic_code_area.rb
|
391
|
+
- examples/basic_code_entry.rb
|
391
392
|
- examples/basic_composite_shape.rb
|
392
393
|
- examples/basic_custom_shape.rb
|
393
394
|
- examples/basic_draw_text.rb
|
@@ -592,6 +593,7 @@ files:
|
|
592
593
|
- lib/glimmer/libui/control_proxy/window_proxy.rb
|
593
594
|
- lib/glimmer/libui/custom_control.rb
|
594
595
|
- lib/glimmer/libui/custom_control/code_area.rb
|
596
|
+
- lib/glimmer/libui/custom_control/code_entry.rb
|
595
597
|
- lib/glimmer/libui/custom_control/refined_table.rb
|
596
598
|
- lib/glimmer/libui/custom_shape.rb
|
597
599
|
- lib/glimmer/libui/custom_window.rb
|
@@ -611,6 +613,7 @@ files:
|
|
611
613
|
- lib/glimmer/libui/shape/polyline.rb
|
612
614
|
- lib/glimmer/libui/shape/rectangle.rb
|
613
615
|
- lib/glimmer/libui/shape/square.rb
|
616
|
+
- lib/glimmer/libui/syntax_highlighter.rb
|
614
617
|
- lib/glimmer/proc_tracker.rb
|
615
618
|
- lib/glimmer/rake_task.rb
|
616
619
|
- lib/glimmer/rake_task/list.rb
|