glimmer-dsl-libui 0.4.1 → 0.4.5

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -1
  3. data/README.md +1628 -204
  4. data/VERSION +1 -1
  5. data/examples/basic_entry.rb +27 -24
  6. data/examples/basic_entry2.rb +31 -0
  7. data/examples/button_counter.rb +27 -0
  8. data/examples/dynamic_area.rb +77 -90
  9. data/examples/dynamic_area2.rb +14 -12
  10. data/examples/dynamic_area3.rb +90 -0
  11. data/examples/dynamic_area4.rb +95 -0
  12. data/examples/form.rb +42 -30
  13. data/examples/form2.rb +37 -0
  14. data/examples/form_table.rb +100 -87
  15. data/examples/form_table2.rb +93 -0
  16. data/examples/histogram.rb +98 -91
  17. data/examples/histogram2.rb +109 -0
  18. data/examples/login.rb +45 -39
  19. data/examples/login2.rb +55 -0
  20. data/examples/login3.rb +65 -0
  21. data/examples/login4.rb +61 -0
  22. data/examples/login5.rb +43 -0
  23. data/examples/meta_example.rb +10 -7
  24. data/examples/method_based_custom_keyword.rb +8 -15
  25. data/examples/method_based_custom_keyword2.rb +97 -0
  26. data/examples/snake.rb +1 -1
  27. data/examples/timer.rb +28 -31
  28. data/examples/timer2.rb +129 -0
  29. data/glimmer-dsl-libui.gemspec +0 -0
  30. data/lib/glimmer/dsl/libui/data_binding_expression.rb +4 -6
  31. data/lib/glimmer/libui/attributed_string.rb +3 -0
  32. data/lib/glimmer/libui/control_proxy/area_proxy.rb +52 -46
  33. data/lib/glimmer/libui/control_proxy/entry_proxy.rb +5 -0
  34. data/lib/glimmer/libui/control_proxy/image_proxy.rb +4 -5
  35. data/lib/glimmer/libui/control_proxy/multiline_entry_proxy.rb +5 -0
  36. data/lib/glimmer/libui/control_proxy/spinbox_proxy.rb +38 -0
  37. data/lib/glimmer/libui/control_proxy/table_proxy.rb +1 -1
  38. data/lib/glimmer/libui/control_proxy.rb +8 -1
  39. data/lib/glimmer/libui/data_bindable.rb +39 -0
  40. data/lib/glimmer/libui/shape.rb +7 -2
  41. metadata +17 -2
@@ -1,95 +1,108 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'glimmer-dsl-libui'
4
2
 
5
- include Glimmer
6
-
7
- data = [
8
- ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
9
- ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
10
- ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
11
- ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
12
- ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
13
- ]
14
-
15
- window('Contacts', 600, 600) { |w|
16
- margined true
3
+ class FormTable
4
+ include Glimmer
17
5
 
18
- vertical_box {
19
- form {
20
- stretchy false
21
-
22
- @name_entry = entry {
23
- label 'Name'
24
- }
25
-
26
- @email_entry = entry {
27
- label 'Email'
28
- }
29
-
30
- @phone_entry = entry {
31
- label 'Phone'
32
- }
33
-
34
- @city_entry = entry {
35
- label 'City'
36
- }
37
-
38
- @state_entry = entry {
39
- label 'State'
40
- }
41
- }
42
-
43
- button('Save Contact') {
44
- stretchy false
45
-
46
- on_clicked do
47
- new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
48
- if new_row.include?('')
49
- msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
50
- else
51
- data << new_row # automatically inserts a row into the table due to implicit data-binding
52
- @unfiltered_data = data.dup
53
- @name_entry.text = ''
54
- @email_entry.text = ''
55
- @phone_entry.text = ''
56
- @city_entry.text = ''
57
- @state_entry.text = ''
58
- end
59
- end
60
- }
61
-
62
- search_entry { |se|
63
- stretchy false
6
+ attr_accessor :name, :email, :phone, :city, :state, :filter_value
7
+
8
+ def initialize
9
+ @data = [
10
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
11
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
12
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
13
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
14
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
15
+ ]
16
+ end
17
+
18
+ def launch
19
+ window('Contacts', 600, 600) { |w|
20
+ margined true
64
21
 
65
- on_changed do
66
- filter_value = se.text
67
- @unfiltered_data ||= data.dup
68
- # Unfilter first to remove any previous filters
69
- data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
70
- # Now, apply filter if entered
71
- unless filter_value.empty?
72
- data.filter! do |row_data| # affects table indirectly through implicit data-binding
73
- row_data.any? do |cell|
74
- cell.to_s.downcase.include?(filter_value.downcase)
22
+ vertical_box {
23
+ form {
24
+ stretchy false
25
+
26
+ entry {
27
+ label 'Name'
28
+ text <=> [self, :name]
29
+ }
30
+
31
+ entry {
32
+ label 'Email'
33
+ text <=> [self, :email]
34
+ }
35
+
36
+ entry {
37
+ label 'Phone'
38
+ text <=> [self, :phone]
39
+ }
40
+
41
+ entry {
42
+ label 'City'
43
+ text <=> [self, :city]
44
+ }
45
+
46
+ entry {
47
+ label 'State'
48
+ text <=> [self, :state]
49
+ }
50
+ }
51
+
52
+ button('Save Contact') {
53
+ stretchy false
54
+
55
+ on_clicked do
56
+ new_row = [name, email, phone, city, state]
57
+ if new_row.include?('')
58
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
59
+ else
60
+ @data << new_row # automatically inserts a row into the table due to implicit data-binding
61
+ @unfiltered_data = @data.dup
62
+ self.name = '' # automatically clears name entry through explicit data-binding
63
+ self.email = ''
64
+ self.phone = ''
65
+ self.city = ''
66
+ self.state = ''
75
67
  end
76
68
  end
77
- end
78
- end
79
- }
69
+ }
70
+
71
+ search_entry {
72
+ stretchy false
73
+ text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
74
+ after_write: ->(filter_value) { # execute after write to self.filter_value
75
+ @unfiltered_data ||= @data.dup
76
+ # Unfilter first to remove any previous filters
77
+ @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
78
+ # Now, apply filter if entered
79
+ unless filter_value.empty?
80
+ @data.filter! do |row_data| # affects table indirectly through implicit data-binding
81
+ row_data.any? do |cell|
82
+ cell.to_s.downcase.include?(filter_value.downcase)
83
+ end
84
+ end
85
+ end
86
+ }
87
+ ]
88
+ }
89
+
90
+ table {
91
+ text_column('Name')
92
+ text_column('Email')
93
+ text_column('Phone')
94
+ text_column('City')
95
+ text_column('State')
80
96
 
81
- table {
82
- text_column('Name')
83
- text_column('Email')
84
- text_column('Phone')
85
- text_column('City')
86
- text_column('State')
97
+ cell_rows @data # implicit data-binding
98
+
99
+ on_changed do |row, type, row_data|
100
+ puts "Row #{row} #{type}: #{row_data}"
101
+ end
102
+ }
103
+ }
104
+ }.show
105
+ end
106
+ end
87
107
 
88
- cell_rows data # implicit data-binding
89
-
90
- on_changed do |row, type, row_data|
91
- puts "Row #{row} #{type}: #{row_data}"
92
- end
93
- }
94
- }
95
- }.show
108
+ FormTable.new.launch
@@ -0,0 +1,93 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ include Glimmer
4
+
5
+ data = [
6
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
7
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
8
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
9
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
10
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
11
+ ]
12
+
13
+ window('Contacts', 600, 600) { |w|
14
+ margined true
15
+
16
+ vertical_box {
17
+ form {
18
+ stretchy false
19
+
20
+ @name_entry = entry {
21
+ label 'Name'
22
+ }
23
+
24
+ @email_entry = entry {
25
+ label 'Email'
26
+ }
27
+
28
+ @phone_entry = entry {
29
+ label 'Phone'
30
+ }
31
+
32
+ @city_entry = entry {
33
+ label 'City'
34
+ }
35
+
36
+ @state_entry = entry {
37
+ label 'State'
38
+ }
39
+ }
40
+
41
+ button('Save Contact') {
42
+ stretchy false
43
+
44
+ on_clicked do
45
+ new_row = [@name_entry.text, @email_entry.text, @phone_entry.text, @city_entry.text, @state_entry.text]
46
+ if new_row.include?('')
47
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
48
+ else
49
+ data << new_row # automatically inserts a row into the table due to implicit data-binding
50
+ @unfiltered_data = data.dup
51
+ @name_entry.text = ''
52
+ @email_entry.text = ''
53
+ @phone_entry.text = ''
54
+ @city_entry.text = ''
55
+ @state_entry.text = ''
56
+ end
57
+ end
58
+ }
59
+
60
+ search_entry { |se|
61
+ stretchy false
62
+
63
+ on_changed do
64
+ filter_value = se.text
65
+ @unfiltered_data ||= data.dup
66
+ # Unfilter first to remove any previous filters
67
+ data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
68
+ # Now, apply filter if entered
69
+ unless filter_value.empty?
70
+ data.filter! do |row_data| # affects table indirectly through implicit data-binding
71
+ row_data.any? do |cell|
72
+ cell.to_s.downcase.include?(filter_value.downcase)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ }
78
+
79
+ table {
80
+ text_column('Name')
81
+ text_column('Email')
82
+ text_column('Phone')
83
+ text_column('City')
84
+ text_column('State')
85
+
86
+ cell_rows data # implicit data-binding
87
+
88
+ on_changed do |row, type, row_data|
89
+ puts "Row #{row} #{type}: #{row_data}"
90
+ end
91
+ }
92
+ }
93
+ }.show
@@ -2,106 +2,113 @@
2
2
 
3
3
  require 'glimmer-dsl-libui'
4
4
 
5
- include Glimmer
6
-
7
- X_OFF_LEFT = 20
8
- Y_OFF_TOP = 20
9
- X_OFF_RIGHT = 20
10
- Y_OFF_BOTTOM = 20
11
- POINT_RADIUS = 5
12
- COLOR_BLUE = 0x1E90FF
13
-
14
- @datapoints = 10.times.map {Random.new.rand(90)}
15
-
16
- def graph_size(area_width, area_height)
17
- graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
18
- graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
19
- [graph_width, graph_height]
20
- end
21
-
22
- def point_locations(width, height)
23
- xincr = width / 9.0 # 10 - 1 to make the last point be at the end
24
- yincr = height / 100.0
25
-
26
- @datapoints.each_with_index.map do |value, i|
27
- val = 100 - value
28
- [xincr * i, yincr * val]
5
+ class Histogram
6
+ include Glimmer
7
+
8
+ X_OFF_LEFT = 20
9
+ Y_OFF_TOP = 20
10
+ X_OFF_RIGHT = 20
11
+ Y_OFF_BOTTOM = 20
12
+ POINT_RADIUS = 5
13
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
14
+
15
+ attr_accessor :datapoints, :histogram_color
16
+
17
+ def initialize
18
+ @datapoints = 10.times.map {Random.new.rand(90)}
19
+ @histogram_color = COLOR_BLUE
29
20
  end
30
- end
31
-
32
- # method-based custom control representing a graph path
33
- def graph_path(width, height, should_extend, &block)
34
- locations = point_locations(width, height).flatten
35
- path {
36
- if should_extend
37
- polygon(locations + [width, height, 0, height])
38
- else
39
- polyline(locations)
21
+
22
+ def graph_size(area_width, area_height)
23
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
24
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
25
+ [graph_width, graph_height]
26
+ end
27
+
28
+ def point_locations(width, height)
29
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
30
+ yincr = height / 100.0
31
+
32
+ @datapoints.each_with_index.map do |value, i|
33
+ val = 100 - value
34
+ [xincr * i, yincr * val]
40
35
  end
41
-
42
- # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
43
- transform {
44
- translate X_OFF_LEFT, Y_OFF_TOP
45
- }
46
-
47
- block.call
48
- }
49
- end
50
-
51
- window('histogram example', 640, 480) {
52
- margined true
36
+ end
53
37
 
54
- horizontal_box {
55
- vertical_box {
56
- stretchy false
57
-
58
- 10.times do |i|
59
- spinbox(0, 100) { |sb|
60
- stretchy false
61
- value @datapoints[i]
62
-
63
- on_changed do
64
- @datapoints[i] = sb.value
65
- @area.queue_redraw_all
66
- end
67
- }
38
+ # method-based custom control representing a graph path
39
+ def graph_path(width, height, should_extend, &block)
40
+ locations = point_locations(width, height).flatten
41
+ path {
42
+ if should_extend
43
+ polygon(locations + [width, height, 0, height])
44
+ else
45
+ polyline(locations)
68
46
  end
69
47
 
70
- @color_button = color_button {
71
- stretchy false
72
- color COLOR_BLUE
73
-
74
- on_changed do
75
- @area.queue_redraw_all
76
- end
48
+ # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
49
+ transform {
50
+ translate X_OFF_LEFT, Y_OFF_TOP
77
51
  }
52
+
53
+ block.call
78
54
  }
79
-
80
- @area = area {
81
- on_draw do |area_draw_params|
82
- rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
83
- fill 0xFFFFFF
84
- }
85
-
86
- graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
55
+ end
56
+
57
+ def launch
58
+ window('histogram example', 640, 480) {
59
+ margined true
87
60
 
88
- figure(X_OFF_LEFT, Y_OFF_TOP) {
89
- line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
90
- line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
61
+ horizontal_box {
62
+ vertical_box {
63
+ stretchy false
91
64
 
92
- stroke 0x000000, thickness: 2, miter_limit: 10
93
- }
94
-
95
- # now create the fill for the graph below the graph line
96
- graph_path(graph_width, graph_height, true) {
97
- fill @color_button.color.merge(a: 0.5)
65
+ 10.times do |i|
66
+ spinbox(0, 100) { |sb|
67
+ stretchy false
68
+ value <=> [self, "datapoints[#{i}]", after_write: -> { @area.queue_redraw_all }]
69
+ }
70
+ end
71
+
72
+ color_button { |cb|
73
+ stretchy false
74
+ color COLOR_BLUE
75
+
76
+ on_changed do
77
+ @histogram_color = cb.color
78
+ @area.queue_redraw_all
79
+ end
80
+ }
98
81
  }
99
82
 
100
- # now draw the histogram line
101
- graph_path(graph_width, graph_height, false) {
102
- stroke @color_button.color.merge(thickness: 2, miter_limit: 10)
83
+ @area = area {
84
+ on_draw do |area_draw_params|
85
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
86
+ fill 0xFFFFFF
87
+ }
88
+
89
+ graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
90
+
91
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
92
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
93
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
94
+
95
+ stroke 0x000000, thickness: 2, miter_limit: 10
96
+ }
97
+
98
+ # now create the fill for the graph below the graph line
99
+ graph_path(graph_width, graph_height, true) {
100
+ fill @histogram_color.merge(a: 0.5)
101
+ }
102
+
103
+ # now draw the histogram line
104
+ graph_path(graph_width, graph_height, false) {
105
+ stroke @histogram_color.merge(thickness: 2, miter_limit: 10)
106
+ }
107
+ end
103
108
  }
104
- end
105
- }
106
- }
107
- }.show
109
+ }
110
+ }.show
111
+ end
112
+ end
113
+
114
+ Histogram.new.launch
@@ -0,0 +1,109 @@
1
+ # https://github.com/jamescook/libui-ruby/blob/master/example/histogram.rb
2
+
3
+ require 'glimmer-dsl-libui'
4
+
5
+ include Glimmer
6
+
7
+ X_OFF_LEFT = 20
8
+ Y_OFF_TOP = 20
9
+ X_OFF_RIGHT = 20
10
+ Y_OFF_BOTTOM = 20
11
+ POINT_RADIUS = 5
12
+ COLOR_BLUE = Glimmer::LibUI.interpret_color(0x1E90FF)
13
+
14
+ @datapoints = 10.times.map {Random.new.rand(90)}
15
+ @color = COLOR_BLUE
16
+
17
+ def graph_size(area_width, area_height)
18
+ graph_width = area_width - X_OFF_LEFT - X_OFF_RIGHT
19
+ graph_height = area_height - Y_OFF_TOP - Y_OFF_BOTTOM
20
+ [graph_width, graph_height]
21
+ end
22
+
23
+ def point_locations(width, height)
24
+ xincr = width / 9.0 # 10 - 1 to make the last point be at the end
25
+ yincr = height / 100.0
26
+
27
+ @datapoints.each_with_index.map do |value, i|
28
+ val = 100 - value
29
+ [xincr * i, yincr * val]
30
+ end
31
+ end
32
+
33
+ # method-based custom control representing a graph path
34
+ def graph_path(width, height, should_extend, &block)
35
+ locations = point_locations(width, height).flatten
36
+ path {
37
+ if should_extend
38
+ polygon(locations + [width, height, 0, height])
39
+ else
40
+ polyline(locations)
41
+ end
42
+
43
+ # apply a transform to the coordinate space for this path so (0, 0) is the top-left corner of the graph
44
+ transform {
45
+ translate X_OFF_LEFT, Y_OFF_TOP
46
+ }
47
+
48
+ block.call
49
+ }
50
+ end
51
+
52
+ window('histogram example', 640, 480) {
53
+ margined true
54
+
55
+ horizontal_box {
56
+ vertical_box {
57
+ stretchy false
58
+
59
+ 10.times do |i|
60
+ spinbox(0, 100) { |sb|
61
+ stretchy false
62
+ value @datapoints[i]
63
+
64
+ on_changed do
65
+ @datapoints[i] = sb.value
66
+ @area.queue_redraw_all
67
+ end
68
+ }
69
+ end
70
+
71
+ color_button { |cb|
72
+ stretchy false
73
+ color COLOR_BLUE
74
+
75
+ on_changed do
76
+ @color = cb.color
77
+ @area.queue_redraw_all
78
+ end
79
+ }
80
+ }
81
+
82
+ @area = area {
83
+ on_draw do |area_draw_params|
84
+ rectangle(0, 0, area_draw_params[:area_width], area_draw_params[:area_height]) {
85
+ fill 0xFFFFFF
86
+ }
87
+
88
+ graph_width, graph_height = *graph_size(area_draw_params[:area_width], area_draw_params[:area_height])
89
+
90
+ figure(X_OFF_LEFT, Y_OFF_TOP) {
91
+ line(X_OFF_LEFT, Y_OFF_TOP + graph_height)
92
+ line(X_OFF_LEFT + graph_width, Y_OFF_TOP + graph_height)
93
+
94
+ stroke 0x000000, thickness: 2, miter_limit: 10
95
+ }
96
+
97
+ # now create the fill for the graph below the graph line
98
+ graph_path(graph_width, graph_height, true) {
99
+ fill @color.merge(a: 0.5)
100
+ }
101
+
102
+ # now draw the histogram line
103
+ graph_path(graph_width, graph_height, false) {
104
+ stroke @color.merge(thickness: 2, miter_limit: 10)
105
+ }
106
+ end
107
+ }
108
+ }
109
+ }.show
data/examples/login.rb CHANGED
@@ -1,45 +1,51 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'glimmer-dsl-libui'
4
2
 
5
- include Glimmer
6
-
7
- window('Login') {
8
- margined true
3
+ class Login
4
+ include Glimmer
9
5
 
10
- vertical_box {
11
- form {
12
- @username_entry = entry {
13
- label 'Username:'
14
- }
15
-
16
- @password_entry = password_entry {
17
- label 'Password:'
18
- }
19
- }
20
-
21
- horizontal_box {
22
- @login_button = button('Login') {
23
- on_clicked do
24
- @username_entry.enabled = false
25
- @password_entry.enabled = false
26
- @login_button.enabled = false
27
- @logout_button.enabled = true
28
- end
29
- }
6
+ attr_accessor :username, :password, :logged_in
7
+
8
+ def launch
9
+ window('Login') {
10
+ margined true
30
11
 
31
- @logout_button = button('Logout') {
32
- enabled false
12
+ vertical_box {
13
+ form {
14
+ entry {
15
+ label 'Username:'
16
+ text <=> [self, :username]
17
+ enabled <= [self, :logged_in, on_read: :!] # `on_read: :!` negates read value
18
+ }
19
+
20
+ password_entry {
21
+ label 'Password:'
22
+ text <=> [self, :password]
23
+ enabled <= [self, :logged_in, on_read: :!]
24
+ }
25
+ }
33
26
 
34
- on_clicked do
35
- @username_entry.text = ''
36
- @password_entry.text = ''
37
- @username_entry.enabled = true
38
- @password_entry.enabled = true
39
- @login_button.enabled = true
40
- @logout_button.enabled = false
41
- end
27
+ horizontal_box {
28
+ button('Login') {
29
+ enabled <= [self, :logged_in, on_read: :!]
30
+
31
+ on_clicked do
32
+ self.logged_in = true
33
+ end
34
+ }
35
+
36
+ button('Logout') {
37
+ enabled <= [self, :logged_in]
38
+
39
+ on_clicked do
40
+ self.logged_in = false
41
+ self.username = ''
42
+ self.password = ''
43
+ end
44
+ }
45
+ }
42
46
  }
43
- }
44
- }
45
- }.show
47
+ }.show
48
+ end
49
+ end
50
+
51
+ Login.new.launch