glimmer-dsl-opal 0.10.3 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +257 -0
  3. data/README.md +188 -1300
  4. data/VERSION +1 -1
  5. data/app/controllers/glimmer/application_controller.rb +4 -0
  6. data/app/controllers/glimmer/image_paths_controller.rb +41 -0
  7. data/app/views/glimmer/image_paths/index.html.erb +1 -0
  8. data/{lib/glimmer-dsl-opal/samples/hello/hello_computed/contact.rb → config/routes.rb} +2 -20
  9. data/lib/display.rb +3 -0
  10. data/lib/glimmer-dsl-opal.rb +8 -3
  11. data/lib/glimmer-dsl-opal/ext/file.rb +31 -0
  12. data/lib/glimmer-dsl-opal/ext/glimmer/dsl/engine.rb +1 -1
  13. data/lib/glimmer-dsl-opal/samples/elaborate/contact_manager.rb +15 -13
  14. data/lib/glimmer-dsl-opal/samples/elaborate/login.rb +55 -28
  15. data/lib/glimmer-dsl-opal/samples/elaborate/tic_tac_toe.rb +2 -2
  16. data/lib/glimmer-dsl-opal/samples/elaborate/weather.rb +157 -0
  17. data/lib/glimmer-dsl-opal/samples/hello/hello_button.rb +8 -8
  18. data/lib/glimmer-dsl-opal/samples/hello/hello_checkbox.rb +16 -14
  19. data/lib/glimmer-dsl-opal/samples/hello/hello_checkbox_group.rb +14 -9
  20. data/lib/glimmer-dsl-opal/samples/hello/hello_combo.rb +24 -22
  21. data/lib/glimmer-dsl-opal/samples/hello/hello_computed.rb +32 -14
  22. data/lib/glimmer-dsl-opal/samples/hello/hello_custom_shell.rb +16 -12
  23. data/lib/glimmer-dsl-opal/samples/hello/hello_custom_widget.rb +1 -1
  24. data/lib/glimmer-dsl-opal/samples/hello/hello_date_time.rb +4 -4
  25. data/lib/glimmer-dsl-opal/samples/hello/hello_group.rb +6 -6
  26. data/lib/glimmer-dsl-opal/samples/hello/hello_list_multi_selection.rb +1 -1
  27. data/lib/glimmer-dsl-opal/samples/hello/hello_list_single_selection.rb +1 -1
  28. data/lib/glimmer-dsl-opal/samples/hello/hello_radio.rb +18 -16
  29. data/lib/glimmer-dsl-opal/samples/hello/hello_radio_group.rb +17 -12
  30. data/lib/glimmer-dsl-opal/samples/hello/hello_table.rb +31 -23
  31. data/lib/glimmer-dsl-opal/samples/hello/hello_table/baseball_park.png +0 -0
  32. data/lib/glimmer/data_binding/table_items_binding.rb +3 -2
  33. data/lib/glimmer/dsl/opal/bind_expression.rb +24 -25
  34. data/lib/glimmer/dsl/opal/custom_widget_expression.rb +7 -7
  35. data/lib/glimmer/dsl/opal/dsl.rb +2 -0
  36. data/lib/glimmer/dsl/opal/menu_expression.rb +1 -1
  37. data/lib/glimmer/dsl/opal/property_expression.rb +2 -1
  38. data/lib/glimmer/dsl/opal/shape_expression.rb +1 -1
  39. data/lib/glimmer/dsl/opal/shell_expression.rb +1 -1
  40. data/lib/glimmer/dsl/opal/shine_data_binding_expression.rb +49 -0
  41. data/lib/glimmer/dsl/opal/table_items_data_binding_expression.rb +2 -2
  42. data/lib/glimmer/dsl/opal/widget_expression.rb +1 -1
  43. data/lib/glimmer/engine.rb +21 -0
  44. data/lib/glimmer/swt/combo_proxy.rb +1 -0
  45. data/lib/glimmer/swt/composite_proxy.rb +34 -0
  46. data/lib/glimmer/swt/dialog_proxy.rb +1 -1
  47. data/lib/glimmer/swt/display_proxy.rb +1 -0
  48. data/lib/glimmer/swt/layout_proxy.rb +23 -3
  49. data/lib/glimmer/swt/message_box_proxy.rb +1 -1
  50. data/lib/glimmer/swt/shell_proxy.rb +43 -0
  51. data/lib/glimmer/swt/table_item_proxy.rb +3 -0
  52. data/lib/glimmer/swt/table_proxy.rb +19 -3
  53. data/lib/glimmer/swt/widget_proxy.rb +3 -4
  54. data/lib/glimmer/ui/custom_shell.rb +13 -10
  55. data/lib/glimmer/ui/custom_widget.rb +11 -2
  56. data/lib/net/http.rb +15 -6
  57. metadata +20 -12
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.3
1
+ 0.15.1
@@ -0,0 +1,4 @@
1
+ module Glimmer
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,41 @@
1
+ require 'fileutils'
2
+
3
+ module Glimmer
4
+ class ImagePathsController < ApplicationController
5
+ def index
6
+ Gem.loaded_specs.map(&:last).select {|s| s.name == 'glimmer-dsl-opal' || s.dependencies.detect {|dep| dep.name == 'glimmer-dsl-opal'} }
7
+ full_gem_specs = Gem.loaded_specs.map(&:last).select {|s| s.name == 'glimmer-dsl-opal' || s.dependencies.detect {|dep| dep.name == 'glimmer-dsl-swt'} }
8
+ full_gem_paths = full_gem_specs.map {|gem_spec| gem_spec.full_gem_path}
9
+ full_gem_names = full_gem_paths.map {|path| File.basename(path)}
10
+ full_gem_image_path_collections = full_gem_paths.map do |gem_path|
11
+ Dir[File.join(gem_path, '**', '*')].to_a.select {|f| !!f.match(/(png|jpg|jpeg|gif)$/) }
12
+ end
13
+ download_gem_image_path_collections = full_gem_names.size.times.map do |n|
14
+ full_gem_name = full_gem_names[n]
15
+ full_gem_image_paths = full_gem_image_path_collections[n]
16
+ full_gem_image_paths.map do |image_path|
17
+ File.join(full_gem_name, image_path.split(full_gem_name).last)
18
+ end
19
+ end
20
+ download_gem_image_paths = download_gem_image_path_collections.flatten
21
+ download_gem_image_dir_names = download_gem_image_paths.map {|p| File.dirname(p)}.uniq
22
+ download_gem_image_dir_names.each do |image_dir_name|
23
+ FileUtils.mkdir_p(Rails.root.join('app', 'assets', 'images', image_dir_name))
24
+ end
25
+ full_gem_names.size.times.each do |n|
26
+ full_image_paths = full_gem_image_path_collections[n]
27
+ download_image_paths = download_gem_image_path_collections[n]
28
+ full_image_paths.each_with_index do |image_path, i|
29
+ download_image_path = download_image_paths[i]
30
+ image_dir_name = File.dirname(image_path)
31
+ FileUtils.cp_r(image_path, Rails.root.join('app', 'assets', 'images', download_image_path)) # TODO check first if files match and avoid copying if so to save time
32
+ end
33
+ end
34
+ download_gem_image_paths = download_gem_image_paths.map {|p| "/assets/#{p}"}
35
+
36
+ # TODO apply a security white list
37
+ render json: download_gem_image_paths
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1 @@
1
+ NADA
@@ -19,24 +19,6 @@
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
- class HelloComputed
23
- class Contact
24
- attr_accessor :first_name, :last_name, :year_of_birth
25
-
26
- def initialize(attribute_map)
27
- @first_name = attribute_map[:first_name]
28
- @last_name = attribute_map[:last_name]
29
- @year_of_birth = attribute_map[:year_of_birth]
30
- end
31
-
32
- def name
33
- "#{last_name}, #{first_name}"
34
- end
35
-
36
- def age
37
- Time.now.year - year_of_birth.to_i
38
- rescue
39
- 0
40
- end
41
- end
22
+ Glimmer::Engine.routes.draw do
23
+ resources :image_paths, only: [:index]
42
24
  end
data/lib/display.rb CHANGED
@@ -24,8 +24,11 @@ class Display
24
24
  def setAppName(app_name)
25
25
  # No Op in Opal
26
26
  end
27
+ alias app_name= setAppName
28
+
27
29
  def setAppVersion(version)
28
30
  # No Op in Opal
29
31
  end
32
+ alias app_version= setAppVersion
30
33
  end
31
34
  end
@@ -37,6 +37,10 @@ if RUBY_ENGINE == 'opal'
37
37
  def include_package(package)
38
38
  # No Op (just a shim)
39
39
  end
40
+
41
+ def __dir__
42
+ '(dir)'
43
+ end
40
44
  end
41
45
 
42
46
  require 'opal-parser'
@@ -56,6 +60,7 @@ if RUBY_ENGINE == 'opal'
56
60
  # require 'glimmer-dsl-opal/vendor/jquery-ui/jquery-ui.theme.min.css'
57
61
  require 'opal-jquery'
58
62
  require 'opal/jquery/local_storage'
63
+ require 'promise'
59
64
 
60
65
  require 'facets/hash/symbolize_keys'
61
66
  require 'glimmer-dsl-opal/ext/class'
@@ -80,11 +85,12 @@ if RUBY_ENGINE == 'opal'
80
85
  require 'glimmer/config/opal_logger'
81
86
  require 'glimmer-dsl-xml'
82
87
  require 'glimmer-dsl-css'
88
+
83
89
  Element.alias_native :replace_with, :replaceWith
84
90
  Element.alias_native :select
85
91
  Element.alias_native :dialog
86
-
87
- Glimmer::Config.loop_max_count = 300 # TODO disable
92
+
93
+ Glimmer::Config.loop_max_count = 250 # TODO disable
88
94
 
89
95
  original_logger_level = Glimmer::Config.logger.level
90
96
  Glimmer::Config.logger = Glimmer::Config::OpalLogger.new(STDOUT)
@@ -95,7 +101,6 @@ if RUBY_ENGINE == 'opal'
95
101
  result ||= method == '<<'
96
102
  result ||= method == 'handle'
97
103
  end
98
-
99
104
  else
100
105
  require_relative 'glimmer/engine'
101
106
  end
@@ -19,11 +19,42 @@
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 'net/http'
23
+
22
24
  class File
23
25
  class << self
26
+ REGEXP_DIR_FILE = /\(dir\)|\(file\)/
27
+
28
+ attr_accessor :image_paths
29
+
24
30
  def read(*args, &block)
25
31
  # TODO implement via asset downloads in the future
26
32
  # No Op in Opal
27
33
  end
34
+
35
+ # Include special processing for images that matches them against a list of available image paths from the server
36
+ # to convert to web paths.
37
+ alias expand_path_without_glimmer expand_path
38
+ def expand_path(path, base=nil)
39
+ get_image_paths unless image_paths
40
+ if base
41
+ path = expand_path_without_glimmer(path, base)
42
+ end
43
+ path_include_dir_or_file = !!path.match(REGEXP_DIR_FILE)
44
+ if !path_include_dir_or_file
45
+ path
46
+ else
47
+ essential_path = path.split('(dir)').last.split('(file)').last
48
+ image_paths.detect do |image_path|
49
+ image_path.include?(essential_path)
50
+ end
51
+ end
52
+ end
53
+
54
+ def get_image_paths
55
+ image_paths_json = Net::HTTP.get(`window.location.origin`, '/glimmer/image_paths.json')
56
+ self.image_paths = JSON.parse(image_paths_json)
57
+ end
58
+
28
59
  end
29
60
  end
@@ -31,7 +31,7 @@ module Glimmer
31
31
  def interpret_expression(expression, keyword, *args, &block)
32
32
  work = lambda do
33
33
  expression.interpret(parent, keyword, *args, &block).tap do |ui_object|
34
- add_content(ui_object, expression, &block)
34
+ add_content(ui_object, expression, keyword, *args, &block)
35
35
  dsl_stack.pop
36
36
  end
37
37
  end
@@ -1,14 +1,14 @@
1
1
  require_relative "contact_manager/contact_manager_presenter"
2
2
 
3
3
  class ContactManager
4
- include Glimmer
4
+ include Glimmer::UI::CustomShell
5
5
 
6
- def initialize
6
+ before_body {
7
7
  @contact_manager_presenter = ContactManagerPresenter.new
8
8
  @contact_manager_presenter.list
9
- end
9
+ }
10
10
 
11
- def launch
11
+ body {
12
12
  shell {
13
13
  text "Contact Manager"
14
14
  composite {
@@ -28,7 +28,7 @@ class ContactManager
28
28
  }
29
29
  text {
30
30
  layout_data :fill, :center, true, false
31
- text bind(@contact_manager_presenter, :first_name)
31
+ text <=> [@contact_manager_presenter, :first_name]
32
32
  on_key_pressed {|key_event|
33
33
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
34
34
  }
@@ -41,7 +41,7 @@ class ContactManager
41
41
  }
42
42
  text {
43
43
  layout_data :fill, :center, true, false
44
- text bind(@contact_manager_presenter, :last_name)
44
+ text <=> [@contact_manager_presenter, :last_name]
45
45
  on_key_pressed {|key_event|
46
46
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
47
47
  }
@@ -54,7 +54,7 @@ class ContactManager
54
54
  }
55
55
  text {
56
56
  layout_data :fill, :center, true, false
57
- text bind(@contact_manager_presenter, :email)
57
+ text <=> [@contact_manager_presenter, :email]
58
58
  on_key_pressed {|key_event|
59
59
  @contact_manager_presenter.find if key_event.keyCode == swt(:cr)
60
60
  }
@@ -87,7 +87,7 @@ class ContactManager
87
87
  }
88
88
  }
89
89
 
90
- table(:multi) { |table_proxy|
90
+ table(:editable, :multi) { |table_proxy|
91
91
  layout_data {
92
92
  horizontal_alignment :fill
93
93
  vertical_alignment :fill
@@ -95,6 +95,7 @@ class ContactManager
95
95
  grab_excess_vertical_space true
96
96
  height_hint 200
97
97
  }
98
+
98
99
  table_column {
99
100
  text "First Name"
100
101
  width 80
@@ -107,15 +108,16 @@ class ContactManager
107
108
  text "Email"
108
109
  width 200
109
110
  }
110
- items bind(@contact_manager_presenter, :results),
111
- column_properties(:first_name, :last_name, :email)
111
+
112
+ items <=> [@contact_manager_presenter, :results, column_attributes: [:first_name, :last_name, :email]]
113
+
112
114
  on_mouse_up { |event|
113
115
  table_proxy.edit_table_item(event.table_item, event.column_index)
114
116
  }
115
117
  }
116
118
  }
117
- }.open
118
- end
119
+ }
120
+ }
119
121
  end
120
122
 
121
- ContactManager.new.launch
123
+ ContactManager.launch
@@ -1,4 +1,25 @@
1
- require "observer"
1
+ # Copyright (c) 2020-2021 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 'glimmer-dsl-swt'
2
23
 
3
24
  class LoginPresenter
4
25
 
@@ -14,29 +35,26 @@ class LoginPresenter
14
35
 
15
36
  def status=(status)
16
37
  @status = status
17
-
18
- notify_observers("logged_in")
19
- notify_observers("logged_out")
20
38
  end
21
39
 
22
40
  def valid?
23
41
  !@user_name.to_s.strip.empty? && !@password.to_s.strip.empty?
24
42
  end
25
43
 
26
- def logged_in
44
+ def logged_in?
27
45
  self.status == "Logged In"
28
46
  end
29
47
 
30
- def logged_out
31
- !self.logged_in
48
+ def logged_out?
49
+ !self.logged_in?
32
50
  end
33
51
 
34
- def login
52
+ def login!
35
53
  return unless valid?
36
54
  self.status = "Logged In"
37
55
  end
38
56
 
39
- def logout
57
+ def logout!
40
58
  self.user_name = ""
41
59
  self.password = ""
42
60
  self.status = "Logged Out"
@@ -45,19 +63,24 @@ class LoginPresenter
45
63
  end
46
64
 
47
65
  class Login
48
- include Glimmer
66
+ include Glimmer::UI::CustomShell
67
+
68
+ before_body {
69
+ @presenter = LoginPresenter.new
70
+ }
49
71
 
50
- def launch
51
- presenter = LoginPresenter.new
52
- @shell = shell {
72
+ body {
73
+ shell {
53
74
  text "Login"
75
+
54
76
  composite {
55
77
  grid_layout 2, false #two columns with differing widths
56
78
 
57
79
  label { text "Username:" } # goes in column 1
58
80
  @user_name_text = text { # goes in column 2
59
- text bind(presenter, :user_name)
60
- enabled bind(presenter, :logged_out)
81
+ text <=> [@presenter, :user_name]
82
+ enabled <= [@presenter, :logged_out?, computed_by: :status]
83
+
61
84
  on_key_pressed { |event|
62
85
  @password_text.set_focus if event.keyCode == swt(:cr)
63
86
  }
@@ -65,40 +88,44 @@ class Login
65
88
 
66
89
  label { text "Password:" }
67
90
  @password_text = text(:password, :border) {
68
- text bind(presenter, :password)
69
- enabled bind(presenter, :logged_out)
91
+ text <=> [@presenter, :password]
92
+ enabled <= [@presenter, :logged_out?, computed_by: :status]
93
+
70
94
  on_key_pressed { |event|
71
- presenter.login if event.keyCode == swt(:cr)
95
+ @presenter.login! if event.keyCode == swt(:cr)
72
96
  }
73
97
  }
74
98
 
75
99
  label { text "Status:" }
76
- label { text bind(presenter, :status) }
100
+ label { text <= [@presenter, :status] }
77
101
 
78
102
  button {
79
103
  text "Login"
80
- enabled bind(presenter, :logged_out)
81
- on_widget_selected { presenter.login }
104
+ enabled <= [@presenter, :logged_out?, computed_by: :status]
105
+
106
+ on_widget_selected { @presenter.login! }
82
107
  on_key_pressed { |event|
83
- presenter.login if event.keyCode == swt(:cr)
108
+ if event.keyCode == swt(:cr)
109
+ @presenter.login!
110
+ end
84
111
  }
85
112
  }
86
113
 
87
114
  button {
88
115
  text "Logout"
89
- enabled bind(presenter, :logged_in)
90
- on_widget_selected { presenter.logout }
116
+ enabled <= [@presenter, :logged_in?, computed_by: :status]
117
+
118
+ on_widget_selected { @presenter.logout! }
91
119
  on_key_pressed { |event|
92
120
  if event.keyCode == swt(:cr)
93
- presenter.logout
121
+ @presenter.logout!
94
122
  @user_name_text.set_focus
95
123
  end
96
124
  }
97
125
  }
98
126
  }
99
127
  }
100
- @shell.open
101
- end
128
+ }
102
129
  end
103
130
 
104
- Login.new.launch
131
+ Login.launch
@@ -35,8 +35,8 @@ class TicTacToe
35
35
  (1..3).each { |column|
36
36
  button {
37
37
  layout_data :fill, :fill, true, true
38
- text bind(@tic_tac_toe_board[row, column], :sign)
39
- enabled bind(@tic_tac_toe_board[row, column], :empty)
38
+ text <= [@tic_tac_toe_board[row, column], :sign]
39
+ enabled <= [@tic_tac_toe_board[row, column], :empty]
40
40
  font style: :bold, height: 20
41
41
  on_widget_selected {
42
42
  @tic_tac_toe_board.mark(row, column)
@@ -0,0 +1,157 @@
1
+ # Copyright (c) 2020-2021 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 'net/http'
23
+ require 'json'
24
+ require 'facets/string/titlecase'
25
+
26
+ class Weather
27
+ include Glimmer::UI::CustomShell
28
+
29
+ DEFAULT_FONT_HEIGHT = 30
30
+ DEFAULT_FOREGROUND = :white
31
+ DEFAULT_BACKGROUND = rgb(135, 176, 235)
32
+
33
+ attr_accessor :city, :temp, :temp_min, :temp_max, :feels_like, :humidity
34
+
35
+ before_body {
36
+ @weather_mutex = Mutex.new
37
+ self.city = 'Montreal, QC, CA'
38
+ fetch_weather!
39
+ }
40
+
41
+ body {
42
+ shell(:no_resize) {
43
+ grid_layout
44
+
45
+ text 'Glimmer Weather'
46
+ minimum_size 400, 300
47
+ background DEFAULT_BACKGROUND
48
+
49
+ text {
50
+ layout_data(:center, :center, true, true)
51
+
52
+ text <=> [self, :city]
53
+
54
+ on_key_pressed {|event|
55
+ if event.keyCode == swt(:cr) # carriage return
56
+ Thread.new do
57
+ fetch_weather!
58
+ end
59
+ end
60
+ }
61
+ }
62
+
63
+ tab_folder {
64
+ layout_data(:center, :center, true, true)
65
+
66
+ ['℃', '℉'].each do |temp_unit|
67
+ tab_item {
68
+ grid_layout 2, false
69
+
70
+ text temp_unit
71
+ background DEFAULT_BACKGROUND
72
+
73
+ rectangle(0, 0, [:default, -2], [:default, -2], 15, 15) {
74
+ foreground DEFAULT_FOREGROUND
75
+ }
76
+
77
+ %w[temp temp_min temp_max feels_like].each do |field_name|
78
+ temp_field(field_name, temp_unit)
79
+ end
80
+
81
+ humidity_field
82
+ }
83
+ end
84
+ }
85
+ }
86
+ }
87
+
88
+ def temp_field(field_name, temp_unit)
89
+ name_label(field_name)
90
+ label {
91
+ layout_data(:fill, :center, true, false)
92
+ text <= [self, field_name, on_read: ->(t) { "#{kelvin_to_temp_unit(t, temp_unit).to_f.round}°" }]
93
+ font height: DEFAULT_FONT_HEIGHT
94
+ foreground DEFAULT_FOREGROUND
95
+ }
96
+ end
97
+
98
+ def humidity_field
99
+ name_label('humidity')
100
+ label {
101
+ layout_data(:fill, :center, true, false)
102
+ text <= [self, 'humidity', on_read: ->(h) { "#{h.to_f.round}%" }]
103
+ font height: DEFAULT_FONT_HEIGHT
104
+ foreground DEFAULT_FOREGROUND
105
+ }
106
+ end
107
+
108
+ def name_label(field_name)
109
+ label {
110
+ layout_data :fill, :center, false, false
111
+ text field_name.titlecase
112
+ font height: DEFAULT_FONT_HEIGHT
113
+ foreground DEFAULT_FOREGROUND
114
+ }
115
+ end
116
+
117
+ def fetch_weather!
118
+ @weather_mutex.synchronize do
119
+ self.weather_data = JSON.parse(Net::HTTP.get('api.openweathermap.org', "/data/2.5/weather?q=#{city}&appid=1d16d70a9aec3570b5cbd27e6b421330"))
120
+ end
121
+ rescue => e
122
+ Glimmer::Config.logger.error "Unable to fetch weather due to error: #{e.full_message}"
123
+ end
124
+
125
+ def weather_data=(data)
126
+ @weather_data = data
127
+ main_data = data['main']
128
+ # temps come back in Kelvin
129
+ self.temp = main_data['temp']
130
+ self.temp_min = main_data['temp_min']
131
+ self.temp_max = main_data['temp_max']
132
+ self.feels_like = main_data['feels_like']
133
+ self.humidity = main_data['humidity']
134
+ end
135
+
136
+ def kelvin_to_temp_unit(kelvin, temp_unit)
137
+ temp_unit == '℃' ? kelvin_to_celsius(kelvin) : kelvin_to_fahrenheit(kelvin)
138
+ end
139
+
140
+ def kelvin_to_celsius(kelvin)
141
+ return nil if kelvin.nil?
142
+ kelvin - 273.15
143
+ end
144
+
145
+ def celsius_to_fahrenheit(celsius)
146
+ return nil if celsius.nil?
147
+ (celsius * 9 / 5 ) + 32
148
+ end
149
+
150
+ def kelvin_to_fahrenheit(kelvin)
151
+ return nil if kelvin.nil?
152
+ celsius_to_fahrenheit(kelvin_to_celsius(kelvin))
153
+ end
154
+
155
+ end
156
+
157
+ Weather.launch