crimson 0.1.0

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/gempush.yml +44 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +4 -0
  5. data/.vscode/launch.json +14 -0
  6. data/CODE_OF_CONDUCT.md +49 -0
  7. data/Gemfile +11 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +41 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/crimson.gemspec +25 -0
  14. data/example/ets.rb +22 -0
  15. data/example/example.rb +66 -0
  16. data/lib/crimson.js +10 -0
  17. data/lib/crimson.rb +5 -0
  18. data/lib/crimson/client.rb +86 -0
  19. data/lib/crimson/icons/close.png +0 -0
  20. data/lib/crimson/icons/hide.png +0 -0
  21. data/lib/crimson/icons/resize.png +0 -0
  22. data/lib/crimson/mash.rb +9 -0
  23. data/lib/crimson/model.rb +98 -0
  24. data/lib/crimson/model_change.rb +20 -0
  25. data/lib/crimson/notification_bus.rb +35 -0
  26. data/lib/crimson/object.rb +196 -0
  27. data/lib/crimson/server.rb +69 -0
  28. data/lib/crimson/utilities.rb +13 -0
  29. data/lib/crimson/version.rb +3 -0
  30. data/lib/crimson/webserver.rb +17 -0
  31. data/lib/crimson/widgets/bottom_resizer.rb +22 -0
  32. data/lib/crimson/widgets/desktop.rb +44 -0
  33. data/lib/crimson/widgets/form.rb +12 -0
  34. data/lib/crimson/widgets/input.rb +10 -0
  35. data/lib/crimson/widgets/left_resizer.rb +27 -0
  36. data/lib/crimson/widgets/resizer.rb +38 -0
  37. data/lib/crimson/widgets/right_resizer.rb +22 -0
  38. data/lib/crimson/widgets/taskbar.rb +0 -0
  39. data/lib/crimson/widgets/titlebar.rb +75 -0
  40. data/lib/crimson/widgets/top_resizer.rb +27 -0
  41. data/lib/crimson/widgets/window.rb +134 -0
  42. data/lib/html/template.html +13 -0
  43. data/lib/javascript/Application.js +13 -0
  44. data/lib/javascript/Logger.js +11 -0
  45. data/lib/javascript/MessageHandler.js +142 -0
  46. data/lib/javascript/ObjectManager.js +33 -0
  47. data/lib/javascript/ServerInteractor.js +42 -0
  48. data/lib/javascript/Utilities.js +26 -0
  49. metadata +135 -0
Binary file
Binary file
Binary file
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hashie'
4
+
5
+ module Crimson
6
+ class Mash < Hashie::Mash
7
+ disable_warnings :display, :drop
8
+ end
9
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hashie'
4
+ require_relative 'model_change'
5
+ require_relative 'utilities'
6
+ require_relative 'mash'
7
+
8
+ module Crimson
9
+ class Model < SimpleDelegator
10
+ attr_reader :observers, :revisions, :local, :revision_number
11
+
12
+ def initialize
13
+ @observers = {}
14
+ @revisions = [Mash.new]
15
+ @revision_number = 1
16
+ @max_number_of_revisions = 2
17
+ @local = Mash.new
18
+
19
+ super(local)
20
+ end
21
+
22
+ def add_observer(observer, handler = :on_commit)
23
+ observers[observer] = observer.method(handler)
24
+ end
25
+
26
+ def remove_observer(observer)
27
+ observers.delete(observer)
28
+ end
29
+
30
+ def notify_observers(changes)
31
+ observers.each { |_observer, handler| handler.call(self, changes) }
32
+ end
33
+
34
+ def modify(modifications = {})
35
+ local.merge(modifications)
36
+ end
37
+
38
+ def changed?(*keys)
39
+ if keys.empty?
40
+ local != master
41
+ else
42
+ keys.map { |key| local[key] != master[key] }.any?(true)
43
+ end
44
+ end
45
+
46
+ def changes(*keys)
47
+ keys = ( master.keys | local.keys ) if keys.empty?
48
+ diff = Mash.new
49
+
50
+ keys.each do |k|
51
+ v1 = master[k]
52
+ v2 = local[k]
53
+ diff[k] = ModelChange.new(v1, v2) if v1 != v2
54
+ end
55
+
56
+ diff
57
+ end
58
+
59
+ def new_changes(*keys)
60
+ changes(*keys).transform_values(&:new_value)
61
+ end
62
+
63
+ def master
64
+ revisions.last
65
+ end
66
+
67
+ def commit!(*keys)
68
+ if changed?(*keys)
69
+ notify_observers(new_changes(*keys))
70
+ apply_changes!(*keys)
71
+ end
72
+ end
73
+
74
+ def apply_changes!(*keys)
75
+ if keys.empty?
76
+ revisions << Utilities.deep_copy(local)
77
+ else
78
+ revisions << Utilities.deep_copy(master)
79
+ keys.each { |key| master[key] = Utilities.deep_copy(local[key]) }
80
+ end
81
+
82
+ @revision_number += 1
83
+ revisions.shift if revisions.length > @max_number_of_revisions
84
+ end
85
+
86
+ def reload!
87
+ @local = Utilities.deep_copy(master)
88
+ end
89
+
90
+ def rollback!
91
+ if revisions.length > 1
92
+ @local = revisions.pop
93
+ else
94
+ reload!
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crimson
4
+ class ModelChange
5
+ attr_reader :old_value, :new_value
6
+
7
+ def initialize(old_value, new_value)
8
+ @old_value = old_value
9
+ @new_value = new_value
10
+ end
11
+
12
+ def inspect
13
+ to_s
14
+ end
15
+
16
+ def to_s
17
+ return "<#{old_value.inspect}, #{new_value.inspect}>"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+
2
+ require 'hashie'
3
+ require_relative 'object'
4
+
5
+ module Crimson
6
+ class NotificationBus
7
+ attr_reader :notification_handlers
8
+
9
+ def initialize
10
+ @notification_handlers = Hashie::Mash.new
11
+ end
12
+
13
+ def register(object)
14
+ raise ArgumentError unless object.is_a?(Crimson::Object)
15
+ raise ArgumentError if notification_handlers.key?(object.id)
16
+
17
+ notification_handlers[object.id] = object.method(:on_event)
18
+ end
19
+
20
+ def unregister(object)
21
+ raise ArgumentError unless object.is_a?(Crimson::Object)
22
+ raise ArgumentError unless notification_handlers.key?(object.id)
23
+
24
+ notification_handlers.delete(object.id)
25
+ end
26
+
27
+ def notify(message)
28
+ unless notification_handlers.key?(message.id)
29
+ raise ArgumentError, "[NotificationBus] Trying to notify unregistered '#{message.id}'."
30
+ end
31
+
32
+ notification_handlers[message.id].call(message)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tree'
4
+ require 'set'
5
+ require_relative 'model'
6
+ require_relative 'utilities'
7
+ require_relative 'mash'
8
+
9
+ module Crimson
10
+ class Object < Model
11
+ attr_reader :id, :tag, :node, :event_handlers, :added_children, :removed_children
12
+
13
+ def initialize(tag)
14
+ super()
15
+
16
+ @id = :"object_#{Utilities.generate_id}"
17
+ @tag = tag.to_sym
18
+ @node = Tree::TreeNode.new(id, self)
19
+ @event_handlers = Mash.new
20
+
21
+ @added_children = Set.new
22
+ @removed_children = Set.new
23
+
24
+ self.style = Mash.new
25
+
26
+ show
27
+ end
28
+
29
+ def hidden?
30
+ style.display.to_sym == :none
31
+ end
32
+
33
+ def shown?
34
+ !hidden?
35
+ end
36
+
37
+ def hide
38
+ style.display = :none
39
+ end
40
+
41
+ def show
42
+ style.display = :block
43
+ end
44
+
45
+ def on_event(message)
46
+ unless event_handlers.key?(message.event)
47
+ raise ArgumentError, "[Object] Trying to handle unknown event '#{message.event}' for '#{id}'."
48
+ end
49
+
50
+ event_handlers[message.event].each { |functor| functor.call(message.data) }
51
+ end
52
+
53
+ def on(event, handler = nil, &block)
54
+ raise ArgumentError unless handler.nil? || handler.is_a?(Method) || handler.is_a?(Proc)
55
+
56
+ event_handlers[event] = [] unless event_handlers[event]
57
+ event_handlers[event] << handler unless handler.nil?
58
+ event_handlers[event] << block if block_given?
59
+
60
+ self[:events] = event_handlers.keys
61
+ commit!(:events)
62
+ end
63
+
64
+ def un(event, handler = nil)
65
+ raise ArgumentError unless event_handlers.key?(event)
66
+
67
+ event_handlers[event].delete(handler) if handler
68
+
69
+ if event_handlers[event].empty? || handler.nil?
70
+ event_handlers.delete(event)
71
+ end
72
+
73
+ self[:events] = event_handlers.keys
74
+ commit!(:events)
75
+ end
76
+
77
+ def parent
78
+ node.parent&.content
79
+ end
80
+
81
+ def parent=(new_parent)
82
+ raise ArgumentError unless new_parent.nil? || new_parent.is_a?(Crimson::Object)
83
+
84
+ parent&.remove(self)
85
+ new_parent&.add(self)
86
+ end
87
+
88
+ def add(child, at_index = -1)
89
+ raise ArgumentError unless child.is_a?(Crimson::Object)
90
+ raise ArgumentError if children.include?(child)
91
+
92
+ node.add(child.node, at_index)
93
+
94
+ self[:children] = children.map(&:id)
95
+
96
+ added_children << child
97
+ end
98
+
99
+ def remove(child)
100
+ raise ArgumentError unless child.is_a?(Crimson::Object)
101
+ raise ArgumentError unless children.include?(child)
102
+
103
+ node.remove!(child.node)
104
+
105
+ self[:children] = children.map(&:id)
106
+
107
+ removed_children << child
108
+ end
109
+
110
+ def move(child, at_index)
111
+ raise ArgumentError unless child.is_a?(Crimson::Object)
112
+ raise ArgumentError unless children.include?(child)
113
+
114
+ remove(child)
115
+ add(child, at_index)
116
+
117
+ added_children.delete(child)
118
+ removed_children.delete(child)
119
+ end
120
+
121
+ def siblings
122
+ node.siblings.map(&:content)
123
+ end
124
+
125
+ def children
126
+ node.children.map(&:content)
127
+ end
128
+
129
+ def root
130
+ node.root.content
131
+ end
132
+
133
+ def commit_tree!(*keys)
134
+ # TODO: Unsure if this algorithm works for moved objects
135
+ # eg. added, removed, then added again, under the same commit.
136
+
137
+ breadth_each do |object|
138
+ observers.keys.each do |observer|
139
+ object.added_children.each do |child|
140
+ observer.observe(child)
141
+ end
142
+
143
+ object.removed_children.each do |child|
144
+ observer.unobserve(child)
145
+ end
146
+ end
147
+
148
+ object.added_children.clear
149
+ object.removed_children.clear
150
+ object.commit!(*keys)
151
+ end
152
+ end
153
+
154
+ def breadth_each
155
+ node.breadth_each do |subnode|
156
+ yield(subnode.content)
157
+ end
158
+ end
159
+
160
+ def postordered_each
161
+ node.postordered_each do |subnode|
162
+ yield(subnode.content)
163
+ end
164
+ end
165
+
166
+ def preordered_each
167
+ node.preordered_each do |subnode|
168
+ yield(subnode.content)
169
+ end
170
+ end
171
+
172
+ def find_descendant(descendent_id)
173
+ breadth_each { |descendent| return descendent if descendent_id == descendent.id }
174
+ end
175
+
176
+ def inspect
177
+ to_s
178
+ end
179
+
180
+ def to_s
181
+ id.to_s
182
+ end
183
+
184
+ def ==(other)
185
+ other.is_a?(Object) && other.id == id
186
+ end
187
+
188
+ def eql?(other)
189
+ self == other
190
+ end
191
+
192
+ def hash
193
+ id.hash
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thin'
4
+ require 'websocket-eventmachine-server'
5
+ require_relative 'client'
6
+ require_relative 'webserver'
7
+ require_relative 'utilities'
8
+
9
+ module Crimson
10
+ class Server
11
+ attr_reader :clients
12
+
13
+ def initialize(opts = {})
14
+ @opts = opts || {}
15
+ @clients = {}
16
+ end
17
+
18
+ def host
19
+ @opts[:host] || '0.0.0.0'
20
+ end
21
+
22
+ def on_connect(&block)
23
+ @on_connect = block if block_given?
24
+ end
25
+
26
+ def on_disconnect(&block)
27
+ @on_disconnect = block if block_given?
28
+ end
29
+
30
+ def port
31
+ @opts[:port] || 10_000
32
+ end
33
+
34
+ def run(websocket_enabled: true, webserver_enabled: true)
35
+ EM.run do
36
+ start_websocket if websocket_enabled
37
+ start_webserver if webserver_enabled
38
+ end
39
+ end
40
+
41
+ def start_webserver
42
+ template = File.read("#{__dir__}/../html/template.html")
43
+ template.sub!("{PORT}", websocket_port.to_s)
44
+
45
+ Thin::Server.start(WebServer.new(template), host, port, signals: false)
46
+ end
47
+
48
+ def start_websocket
49
+ WebSocket::EventMachine::Server.start(host: host, port: websocket_port) do |ws|
50
+ id = :"client_#{Utilities.generate_id}"
51
+ client = Client.new(id, ws)
52
+
53
+ ws.onopen {
54
+ clients[id] = client
55
+ @on_connect.call(client)
56
+ }
57
+
58
+ ws.onclose {
59
+ @on_disconnect.call(client)
60
+ clients.delete(id)
61
+ }
62
+ end
63
+ end
64
+
65
+ def websocket_port
66
+ port + 1
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crimson
4
+ module Utilities
5
+ def self.generate_id
6
+ (Time.now.to_f * 100_000_000.0).to_i
7
+ end
8
+
9
+ def self.deep_copy(o)
10
+ Marshal.load(Marshal.dump(o))
11
+ end
12
+ end
13
+ end