cura 0.0.1
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 +7 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +122 -0
- data/LICENSE +20 -0
- data/README.md +159 -0
- data/Rakefile +42 -0
- data/cura.gemspec +23 -0
- data/examples/box_model/bin/box_model +11 -0
- data/examples/box_model/debug.log +0 -0
- data/examples/box_model/lib/box_model.rb +21 -0
- data/examples/box_model/lib/box_model/application.rb +24 -0
- data/examples/hello_world/bin/hello_world +10 -0
- data/examples/hello_world/lib/hello_world.rb +201 -0
- data/examples/mruby-examples/README.md +10 -0
- data/examples/mruby-examples/include/cura_examples.h +6 -0
- data/examples/mruby-examples/mrbgem.rake +14 -0
- data/examples/mruby-examples/src/gem_init.c +34 -0
- data/examples/mruby-examples/tools/hello_world/hello_world.c +6 -0
- data/examples/todo_list/app.log +9 -0
- data/examples/todo_list/bin/todo_list +26 -0
- data/examples/todo_list/data.db +0 -0
- data/examples/todo_list/debug.log +0 -0
- data/examples/todo_list/lib/todo_list.rb +28 -0
- data/examples/todo_list/lib/todo_list/application.rb +54 -0
- data/examples/todo_list/lib/todo_list/component/header.rb +27 -0
- data/examples/todo_list/lib/todo_list/component/list.rb +50 -0
- data/examples/todo_list/lib/todo_list/component/list_item.rb +28 -0
- data/examples/todo_list/lib/todo_list/component/list_items.rb +97 -0
- data/examples/todo_list/lib/todo_list/component/lists.rb +89 -0
- data/examples/todo_list/lib/todo_list/database.rb +50 -0
- data/examples/todo_list/lib/todo_list/model/list.rb +9 -0
- data/examples/todo_list/lib/todo_list/model/list_item.rb +9 -0
- data/examples/todo_list/profile.html +11354 -0
- data/lib/cura.rb +13 -0
- data/lib/cura/adapter.rb +67 -0
- data/lib/cura/application.rb +245 -0
- data/lib/cura/attributes/has_ancestry.rb +40 -0
- data/lib/cura/attributes/has_application.rb +31 -0
- data/lib/cura/attributes/has_attributes.rb +89 -0
- data/lib/cura/attributes/has_children.rb +113 -0
- data/lib/cura/attributes/has_colors.rb +66 -0
- data/lib/cura/attributes/has_coordinates.rb +49 -0
- data/lib/cura/attributes/has_dimensions.rb +63 -0
- data/lib/cura/attributes/has_events.rb +89 -0
- data/lib/cura/attributes/has_focusability.rb +37 -0
- data/lib/cura/attributes/has_initialize.rb +15 -0
- data/lib/cura/attributes/has_offsets.rb +82 -0
- data/lib/cura/attributes/has_orientation.rb +53 -0
- data/lib/cura/attributes/has_relative_coordinates.rb +39 -0
- data/lib/cura/attributes/has_root.rb +104 -0
- data/lib/cura/attributes/has_side_attributes.rb +95 -0
- data/lib/cura/attributes/has_windows.rb +70 -0
- data/lib/cura/borders.rb +16 -0
- data/lib/cura/color.rb +330 -0
- data/lib/cura/component/base.rb +180 -0
- data/lib/cura/component/button.rb +57 -0
- data/lib/cura/component/group.rb +77 -0
- data/lib/cura/component/label.rb +224 -0
- data/lib/cura/component/listbox.rb +152 -0
- data/lib/cura/component/pack.rb +144 -0
- data/lib/cura/component/scrollbar.rb +184 -0
- data/lib/cura/component/textbox.rb +118 -0
- data/lib/cura/cursor.rb +77 -0
- data/lib/cura/error/base.rb +10 -0
- data/lib/cura/error/invalid_adapter.rb +18 -0
- data/lib/cura/error/invalid_application.rb +18 -0
- data/lib/cura/error/invalid_color.rb +18 -0
- data/lib/cura/error/invalid_component.rb +18 -0
- data/lib/cura/error/invalid_middleware.rb +18 -0
- data/lib/cura/event.rb +38 -0
- data/lib/cura/event/base.rb +108 -0
- data/lib/cura/event/click.rb +14 -0
- data/lib/cura/event/dispatcher.rb +122 -0
- data/lib/cura/event/focus.rb +14 -0
- data/lib/cura/event/handler.rb +74 -0
- data/lib/cura/event/key_down.rb +68 -0
- data/lib/cura/event/middleware/aimer/base.rb +38 -0
- data/lib/cura/event/middleware/aimer/dispatcher_target.rb +24 -0
- data/lib/cura/event/middleware/aimer/mouse_focus.rb +48 -0
- data/lib/cura/event/middleware/aimer/target_option.rb +38 -0
- data/lib/cura/event/middleware/base.rb +21 -0
- data/lib/cura/event/middleware/dispatch.rb +20 -0
- data/lib/cura/event/middleware/translator/base.rb +37 -0
- data/lib/cura/event/middleware/translator/mouse_click.rb +44 -0
- data/lib/cura/event/mouse.rb +34 -0
- data/lib/cura/event/mouse_button.rb +103 -0
- data/lib/cura/event/mouse_wheel_down.rb +14 -0
- data/lib/cura/event/mouse_wheel_up.rb +14 -0
- data/lib/cura/event/resize.rb +17 -0
- data/lib/cura/event/selected.rb +14 -0
- data/lib/cura/event/unfocus.rb +14 -0
- data/lib/cura/focus_controller.rb +89 -0
- data/lib/cura/key.rb +313 -0
- data/lib/cura/margins.rb +16 -0
- data/lib/cura/offsets.rb +91 -0
- data/lib/cura/padding.rb +16 -0
- data/lib/cura/pencil.rb +29 -0
- data/lib/cura/version.rb +6 -0
- data/lib/cura/window.rb +85 -0
- data/spec/cura/attributes/has_ancestry_spec.rb +108 -0
- data/spec/cura/attributes/has_application_spec.rb +59 -0
- data/spec/cura/attributes/has_attributes_spec.rb +75 -0
- data/spec/cura/attributes/has_children_spec.rb +169 -0
- data/spec/cura/attributes/has_colors_spec.rb +20 -0
- data/spec/cura/attributes/has_coordinates_spec.rb +19 -0
- data/spec/cura/attributes/has_dimensions_spec.rb +19 -0
- data/spec/cura/attributes/has_events_spec.rb +18 -0
- data/spec/cura/attributes/has_focusability_spec.rb +58 -0
- data/spec/cura/attributes/has_offsets_spec.rb +18 -0
- data/spec/cura/attributes/has_orientation_spec.rb +102 -0
- data/spec/cura/attributes/has_relative_coordinates_spec.rb +18 -0
- data/spec/cura/attributes/has_side_attributes_spec.rb +19 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/shared_examples_for_attributes.rb +122 -0
- metadata +211 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/attributes/has_attributes"
|
3
|
+
end
|
4
|
+
|
5
|
+
module Cura
|
6
|
+
module Attributes
|
7
|
+
|
8
|
+
# Adds the `orientation` attribute to objects, which can be :vertical or :horizontal.
|
9
|
+
module HasOrientation
|
10
|
+
|
11
|
+
include HasAttributes
|
12
|
+
|
13
|
+
def initialize(attributes={})
|
14
|
+
@orientation = :vertical unless instance_variable_defined?(:@orientation)
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the orientation of this object.
|
20
|
+
#
|
21
|
+
# @return [Symbol]
|
22
|
+
attr_reader :orientation
|
23
|
+
|
24
|
+
# Set the orientation of this object.
|
25
|
+
# Must be :vertical or :horizontal.
|
26
|
+
#
|
27
|
+
# @param [#to_sym] value
|
28
|
+
# @return [Symbol]
|
29
|
+
def orientation=(value)
|
30
|
+
value = value.to_sym
|
31
|
+
raise ArgumentError, "orientation must be one of :vertical or :horizontal" unless [:vertical, :horizontal].include?(value)
|
32
|
+
|
33
|
+
@orientation = value
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check if this object's orientation is set to :horizontal.
|
37
|
+
#
|
38
|
+
# @return [Boolean]
|
39
|
+
def horizontal?
|
40
|
+
orientation == :horizontal
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check if this object's orientation is set to :vertical.
|
44
|
+
#
|
45
|
+
# @return [Boolean]
|
46
|
+
def vertical?
|
47
|
+
orientation == :vertical
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/attributes/has_ancestry"
|
3
|
+
require "cura/attributes/has_coordinates"
|
4
|
+
end
|
5
|
+
|
6
|
+
module Cura
|
7
|
+
module Attributes
|
8
|
+
|
9
|
+
# Adds the `absolute_x` and `absolute_y` attributes, which are relative to it's parent.
|
10
|
+
module HasRelativeCoordinates
|
11
|
+
|
12
|
+
include HasAncestry
|
13
|
+
include HasCoordinates
|
14
|
+
|
15
|
+
def initialize(attributes={})
|
16
|
+
@absolute_x = 0
|
17
|
+
@absolute_y = 0
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get the absolute X coordinate of this object.
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
def absolute_x
|
26
|
+
parent? && parent.respond_to?(:absolute_x) ? @x + parent.offsets.left + parent.absolute_x : @x
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the absolute Y coordinate of this object.
|
30
|
+
#
|
31
|
+
# @return [Integer]
|
32
|
+
def absolute_y
|
33
|
+
parent? && parent.respond_to?(:absolute_y) ? @y + parent.offsets.top + parent.absolute_y : @y
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/attributes/has_attributes"
|
3
|
+
|
4
|
+
require "cura/component/group"
|
5
|
+
end
|
6
|
+
|
7
|
+
module Cura
|
8
|
+
module Attributes
|
9
|
+
|
10
|
+
# Adds the `root` attribute to an object, which defaults to a Component::Group.
|
11
|
+
module HasRoot
|
12
|
+
|
13
|
+
include Attributes::HasAttributes
|
14
|
+
|
15
|
+
def initialize(attributes={})
|
16
|
+
@root = Component::Group.new(parent: self)
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
# @method root
|
22
|
+
# Get root component for this object.
|
23
|
+
#
|
24
|
+
# @return [Component::Group]
|
25
|
+
|
26
|
+
# @method root=(component)
|
27
|
+
# Set root component for this object.
|
28
|
+
#
|
29
|
+
# @param [Component::Group] component
|
30
|
+
# @return [Component::Group]
|
31
|
+
|
32
|
+
attribute(:root) { |component| set_root(component) }
|
33
|
+
|
34
|
+
# Delegates -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
35
|
+
|
36
|
+
# Get the children of this object.
|
37
|
+
#
|
38
|
+
# @return [<Component>]
|
39
|
+
def children(recursive=false)
|
40
|
+
@root.children(recursive)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add a child to this object's root component.
|
44
|
+
#
|
45
|
+
# @param [Component] component
|
46
|
+
# @return [Component]
|
47
|
+
def add_child(component)
|
48
|
+
@root.add_child(component)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add multiple children to this object's root component.
|
52
|
+
#
|
53
|
+
# @param [<Component>] children
|
54
|
+
# @return [<Component>]
|
55
|
+
def add_children(*children)
|
56
|
+
@root.add_children(*children)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remove a child from object's root component at the given index.
|
60
|
+
#
|
61
|
+
# @param [Integer] index
|
62
|
+
# @return [Component]
|
63
|
+
def delete_child_at(index)
|
64
|
+
@root.delete_child_at(index)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Remove a child from this object's root component.
|
68
|
+
#
|
69
|
+
# @param [Component] component
|
70
|
+
# @return [Component]
|
71
|
+
def delete_child(component)
|
72
|
+
@root.delete_child(component)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Remove all children from object's root component.
|
76
|
+
#
|
77
|
+
# @return [HasChildren]
|
78
|
+
def delete_children
|
79
|
+
@root.delete_children
|
80
|
+
end
|
81
|
+
|
82
|
+
# Determine if this object's root component has children.
|
83
|
+
#
|
84
|
+
# @return [Boolean]
|
85
|
+
def children?
|
86
|
+
@root.children?
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def set_root(component)
|
92
|
+
raise TypeError, "root must be a Component::Group" unless component.is_a?(Component::Group)
|
93
|
+
|
94
|
+
@root.parent = nil unless @root.nil?
|
95
|
+
@root = component
|
96
|
+
@root.parent = self
|
97
|
+
|
98
|
+
@root
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/attributes/has_attributes"
|
3
|
+
end
|
4
|
+
|
5
|
+
module Cura
|
6
|
+
module Attributes
|
7
|
+
|
8
|
+
# Adds the `top`, `right`, `bottom`, `left`, `width`, and `height` attributes to objects.
|
9
|
+
module HasSideAttributes
|
10
|
+
|
11
|
+
include HasAttributes
|
12
|
+
|
13
|
+
# @method top
|
14
|
+
# Get the top attribute.
|
15
|
+
#
|
16
|
+
# @return [Integer]
|
17
|
+
|
18
|
+
# @method top=(value)
|
19
|
+
# Set the top attribute.
|
20
|
+
#
|
21
|
+
# @param [#to_i] value
|
22
|
+
# @return [Integer]
|
23
|
+
|
24
|
+
attribute(:top) { |value| validate_size_attribute(value) }
|
25
|
+
|
26
|
+
# @method right
|
27
|
+
# Get the right attribute.
|
28
|
+
#
|
29
|
+
# @return [Integer]
|
30
|
+
|
31
|
+
# @method right=(value)
|
32
|
+
# Set the right attribute.
|
33
|
+
#
|
34
|
+
# @param [#to_i] value
|
35
|
+
# @return [Integer]
|
36
|
+
|
37
|
+
attribute(:right) { |value| validate_size_attribute(value) }
|
38
|
+
|
39
|
+
# @method bottom
|
40
|
+
# Get the bottom attribute.
|
41
|
+
#
|
42
|
+
# @return [Integer]
|
43
|
+
|
44
|
+
# @method bottom=(value)
|
45
|
+
# Set the bottom attribute.
|
46
|
+
#
|
47
|
+
# @param [#to_i] value
|
48
|
+
# @return [Integer]
|
49
|
+
|
50
|
+
attribute(:bottom) { |value| validate_size_attribute(value) }
|
51
|
+
|
52
|
+
# @method left
|
53
|
+
# Get the left attribute.
|
54
|
+
#
|
55
|
+
# @return [Integer]
|
56
|
+
|
57
|
+
# @method left=(value)
|
58
|
+
# Set the left attribute.
|
59
|
+
#
|
60
|
+
# @param [#to_i] value
|
61
|
+
# @return [Integer]
|
62
|
+
|
63
|
+
attribute(:left) { |value| validate_size_attribute(value) }
|
64
|
+
|
65
|
+
def initialize(attributes={})
|
66
|
+
@top = 0 unless instance_variable_defined?(:@top)
|
67
|
+
@right = 0 unless instance_variable_defined?(:@right)
|
68
|
+
@bottom = 0 unless instance_variable_defined?(:@bottom)
|
69
|
+
@left = 0 unless instance_variable_defined?(:@left)
|
70
|
+
|
71
|
+
unless attributes.respond_to?(:to_hash) || attributes.respond_to?(:to_h)
|
72
|
+
attributes = { top: attributes, right: attributes, bottom: attributes, left: attributes } # Set all side attributes to the argument given
|
73
|
+
end
|
74
|
+
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the total height of the attributes.
|
79
|
+
#
|
80
|
+
# @return [Integer]
|
81
|
+
def height
|
82
|
+
@top + @bottom
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get the total width of the attributes.
|
86
|
+
#
|
87
|
+
# @return [Integer]
|
88
|
+
def width
|
89
|
+
@left + @right
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/window"
|
3
|
+
end
|
4
|
+
|
5
|
+
module Cura
|
6
|
+
module Attributes
|
7
|
+
|
8
|
+
# Allows an object to have windows.
|
9
|
+
# TODO: Lots of code is the same as HasChildren
|
10
|
+
module HasWindows
|
11
|
+
|
12
|
+
def initialize(*arguments)
|
13
|
+
@windows = []
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the windows of this object.
|
19
|
+
attr_reader :windows
|
20
|
+
|
21
|
+
# Add a window to this object.
|
22
|
+
#
|
23
|
+
# @param [Window] window
|
24
|
+
# @return [Window]
|
25
|
+
def add_window(window)
|
26
|
+
raise TypeError, "window must be a Cura::Window" unless window.is_a?(Window)
|
27
|
+
|
28
|
+
@windows << window
|
29
|
+
|
30
|
+
window
|
31
|
+
end
|
32
|
+
|
33
|
+
# Remove a window from this object's windows at the given index.
|
34
|
+
#
|
35
|
+
# @param [#to_i] index
|
36
|
+
# @return [Window]
|
37
|
+
def delete_window_at(index)
|
38
|
+
@windows.delete_at(index.to_i)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Remove a window from this object's windows.
|
42
|
+
#
|
43
|
+
# @param [Window] window
|
44
|
+
# @return [Window]
|
45
|
+
def delete_window(window)
|
46
|
+
raise TypeError, "window must be a Cura::Window" unless window.is_a?(Window)
|
47
|
+
index = @windows.index(window)
|
48
|
+
|
49
|
+
delete_window_at(index)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove all windows.
|
53
|
+
def delete_windows
|
54
|
+
(0...@windows.count).to_a.each { |index| delete_window_at(index) }
|
55
|
+
end
|
56
|
+
|
57
|
+
protected # TODO: These should be protected?
|
58
|
+
|
59
|
+
def update_windows
|
60
|
+
windows.each(&:update)
|
61
|
+
end
|
62
|
+
|
63
|
+
def draw_windows
|
64
|
+
windows.each(&:draw)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/cura/borders.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/attributes/has_initialize"
|
3
|
+
require "cura/attributes/has_side_attributes"
|
4
|
+
end
|
5
|
+
|
6
|
+
module Cura
|
7
|
+
|
8
|
+
# The border side attributes of a component.
|
9
|
+
class Borders
|
10
|
+
|
11
|
+
include Attributes::HasInitialize
|
12
|
+
include Attributes::HasSideAttributes
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/cura/color.rb
ADDED
@@ -0,0 +1,330 @@
|
|
1
|
+
if Kernel.respond_to?(:require)
|
2
|
+
require "cura/attributes/has_initialize"
|
3
|
+
require "cura/attributes/has_attributes"
|
4
|
+
end
|
5
|
+
|
6
|
+
module Cura
|
7
|
+
|
8
|
+
# Colors.
|
9
|
+
class Color
|
10
|
+
|
11
|
+
include Attributes::HasInitialize
|
12
|
+
include Attributes::HasAttributes
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# The default color to be overidden by adapters.
|
17
|
+
# Usually, for TUI's to use the terminal theme's colors.
|
18
|
+
# TODO: Remove.
|
19
|
+
def default
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def black
|
24
|
+
new
|
25
|
+
end
|
26
|
+
|
27
|
+
def white
|
28
|
+
new(255, 255, 255)
|
29
|
+
end
|
30
|
+
|
31
|
+
def red
|
32
|
+
new(255, 0, 0)
|
33
|
+
end
|
34
|
+
|
35
|
+
def green
|
36
|
+
new(0, 255, 0)
|
37
|
+
end
|
38
|
+
|
39
|
+
def blue
|
40
|
+
new(0, 0, 255)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(r=0, g=0, b=0, a=255)
|
46
|
+
if r.respond_to?(:to_h)
|
47
|
+
super(r.to_h)
|
48
|
+
else
|
49
|
+
@red = r
|
50
|
+
@green = g
|
51
|
+
@blue = b
|
52
|
+
@alpha = a
|
53
|
+
end
|
54
|
+
|
55
|
+
@lab = rgb_to_lab([@red, @green, @blue]) # TODO: Update on rgb setters?
|
56
|
+
end
|
57
|
+
|
58
|
+
# @method red
|
59
|
+
# Get the red channel of this color.
|
60
|
+
#
|
61
|
+
# @return [Integer]
|
62
|
+
|
63
|
+
# @method red=(value)
|
64
|
+
# Set the red channel of this color.
|
65
|
+
#
|
66
|
+
# @param [#to_i] value
|
67
|
+
# @return [Integer]
|
68
|
+
|
69
|
+
# @method green
|
70
|
+
# Get the green channel of this color.
|
71
|
+
#
|
72
|
+
# @return [Integer]
|
73
|
+
|
74
|
+
# @method green=(value)
|
75
|
+
# Set the green channel of this color.
|
76
|
+
#
|
77
|
+
# @param [#to_i] value
|
78
|
+
# @return [Integer]
|
79
|
+
|
80
|
+
# @method blue=(value)
|
81
|
+
# Get the blue channel of this color.
|
82
|
+
#
|
83
|
+
# @return [Integer]
|
84
|
+
|
85
|
+
# @method blue=(value)
|
86
|
+
# Set the blue channel of this color.
|
87
|
+
#
|
88
|
+
# @param [#to_i] value
|
89
|
+
# @return [Integer]
|
90
|
+
|
91
|
+
# @method alpha
|
92
|
+
# Get the alpha channel of this color.
|
93
|
+
#
|
94
|
+
# @return [Integer]
|
95
|
+
|
96
|
+
# @method alpha=(value)
|
97
|
+
# Set the alpha channel of this color.
|
98
|
+
#
|
99
|
+
# @param [#to_i] value
|
100
|
+
# @return [Integer]
|
101
|
+
|
102
|
+
[:red, :green, :blue, :alpha].each do |channel|
|
103
|
+
attribute(channel) { |value| convert_and_constrain_value(value) }
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_reader :lab
|
107
|
+
|
108
|
+
def -(other)
|
109
|
+
delta_e_2000(@lab, other.lab)
|
110
|
+
end
|
111
|
+
|
112
|
+
def <=>(other)
|
113
|
+
self.hsl[0] <=> other.hsl[0]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Determing if this color is equivalent to another object.
|
117
|
+
#
|
118
|
+
# @param [Object] other
|
119
|
+
# @return [Boolean]
|
120
|
+
def ==(other)
|
121
|
+
other.is_a?(Color) ? matches_color?(other) : super
|
122
|
+
end
|
123
|
+
|
124
|
+
def hsl
|
125
|
+
@hsl ||= rgb_to_hsl(@rgb)
|
126
|
+
end
|
127
|
+
|
128
|
+
def yiq
|
129
|
+
@yiq ||= rgb_to_yiq(@rgb)
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_a
|
133
|
+
[@red, @green, @blue, @alpha]
|
134
|
+
end
|
135
|
+
|
136
|
+
def hex
|
137
|
+
to_a.each_with_object("") { |part, memo| memo << "%02x" % part }
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def matches_color?(other)
|
143
|
+
@alpha == other.alpha && @red == other.red && @green == other.green && @blue && other.blue
|
144
|
+
end
|
145
|
+
|
146
|
+
# Convert the input to an Integer and constrain in or between 0 and 255.
|
147
|
+
def convert_and_constrain_value(value)
|
148
|
+
value = value.to_i
|
149
|
+
|
150
|
+
[255, [0, value].max].min
|
151
|
+
end
|
152
|
+
|
153
|
+
# source: http://www.easyrgb.com/index.php?X=MATH&H=02#text2
|
154
|
+
def rgb_to_xyz(color)
|
155
|
+
r, g, b = color.map do |v|
|
156
|
+
v /= 255.0
|
157
|
+
if v > 0.04045
|
158
|
+
v = ((v + 0.055 ) / 1.055)**2.4
|
159
|
+
else
|
160
|
+
v = v / 12.92
|
161
|
+
end
|
162
|
+
v *= 100
|
163
|
+
end
|
164
|
+
|
165
|
+
#Observer = 2°, Illuminant = D65
|
166
|
+
x = r * 0.4124 + g * 0.3576 + b * 0.1805
|
167
|
+
y = r * 0.2126 + g * 0.7152 + b * 0.0722
|
168
|
+
z = r * 0.0193 + g * 0.1192 + b * 0.9505
|
169
|
+
|
170
|
+
return [x, y, z]
|
171
|
+
end
|
172
|
+
|
173
|
+
def xyz_to_lab(color)
|
174
|
+
f = lambda { |t|
|
175
|
+
return t**(1.0/3) if t > (6.0 / 29)**3
|
176
|
+
return (1.0 / 3) * ((29.0 / 6)**2) * t + (4.0 / 29)
|
177
|
+
}
|
178
|
+
|
179
|
+
x, y, z = color
|
180
|
+
xn, yn, zn = rgb_to_xyz([255, 255, 255])
|
181
|
+
l = 116 * f.call(y/yn) - 16
|
182
|
+
a = 500 * (f.call(x/xn) - f.call(y/yn))
|
183
|
+
b = 200 * (f.call(y/yn) - f.call(z/zn))
|
184
|
+
|
185
|
+
return [l, a, b]
|
186
|
+
end
|
187
|
+
|
188
|
+
def rgb_to_lab(rgb_val)
|
189
|
+
xyz_to_lab rgb_to_xyz rgb_val
|
190
|
+
end
|
191
|
+
|
192
|
+
def rgb_to_hsl(rgb_val)
|
193
|
+
r, g, b = rgb_val.map { |v| v / 255.0 }
|
194
|
+
|
195
|
+
min, max = [r, g, b].minmax
|
196
|
+
delta = max - min
|
197
|
+
|
198
|
+
lig = (max + min) / 2.0
|
199
|
+
|
200
|
+
if delta == 0
|
201
|
+
hue = 0
|
202
|
+
sat = 0
|
203
|
+
else
|
204
|
+
sat = if lig < 0.5
|
205
|
+
delta / (0.0 + (max + min))
|
206
|
+
else
|
207
|
+
delta / (2.0 - max - min)
|
208
|
+
end
|
209
|
+
|
210
|
+
delta_r = (((max - r) / 6.0 ) + (delta / 2.0)) / delta
|
211
|
+
delta_g = (((max - g) / 6.0 ) + (delta / 2.0)) / delta
|
212
|
+
delta_b = (((max - b) / 6.0 ) + (delta / 2.0)) / delta
|
213
|
+
|
214
|
+
hue = case max
|
215
|
+
when r then delta_b - delta_g
|
216
|
+
when g then (1.0/3) + delta_r - delta_b
|
217
|
+
when b then (2.0/3) + delta_g - delta_r
|
218
|
+
end
|
219
|
+
|
220
|
+
hue += 1 if hue < 0
|
221
|
+
hue -= 1 if hue > 1
|
222
|
+
end
|
223
|
+
|
224
|
+
[360 * hue, 100 * sat, 100 * lig]
|
225
|
+
end
|
226
|
+
|
227
|
+
def rgb_to_yiq(rgb_val)
|
228
|
+
r, g, b = rgb_val
|
229
|
+
|
230
|
+
y = 0.299*r + 0.587*g + 0.114*b
|
231
|
+
i = 0.569*r - 0.275*g - 0.321*b
|
232
|
+
q = 0.212*r - 0.523*g + 0.311*b
|
233
|
+
|
234
|
+
[y, i, q]
|
235
|
+
end
|
236
|
+
|
237
|
+
def rad_to_deg(v)
|
238
|
+
(v * 180) / Math::PI
|
239
|
+
end
|
240
|
+
|
241
|
+
def deg_to_rad(v)
|
242
|
+
(v * Math::PI) / 180
|
243
|
+
end
|
244
|
+
|
245
|
+
def lab_to_hue(a, b)
|
246
|
+
bias = 0
|
247
|
+
return 0 if (a >= 0 && b == 0)
|
248
|
+
return 180 if (a < 0 && b == 0)
|
249
|
+
return 90 if (a == 0 && b > 0)
|
250
|
+
return 270 if (a == 0 && b < 0)
|
251
|
+
|
252
|
+
bias = case
|
253
|
+
when a > 0 && b > 0 then 0
|
254
|
+
when a < 0 then 180
|
255
|
+
when a > 0 && b < 0 then 360
|
256
|
+
end
|
257
|
+
|
258
|
+
rad_to_deg(Math.atan(b / a)) + bias
|
259
|
+
end
|
260
|
+
|
261
|
+
def delta_e_2000(lab1, lab2)
|
262
|
+
l1, a1, b1 = lab1
|
263
|
+
l2, a2, b2 = lab2
|
264
|
+
kl, kc, kh = [1, 1, 1]
|
265
|
+
|
266
|
+
xC1 = Math.sqrt(a1**2 + b1**2)
|
267
|
+
xC2 = Math.sqrt(a2**2 + b2**2)
|
268
|
+
xCX = (xC1 + xC2) / 2
|
269
|
+
xGX = 0.5 * (1 - Math.sqrt(xCX**7 / (xCX**7 + 25**7)))
|
270
|
+
xNN = (1 + xGX) * a1
|
271
|
+
xC1 = Math.sqrt(xNN**2 + b1**2)
|
272
|
+
xH1 = lab_to_hue(xNN, b1)
|
273
|
+
xNN = (1 + xGX) * a2
|
274
|
+
xC2 = Math.sqrt(xNN**2 + b2**2)
|
275
|
+
xH2 = lab_to_hue(xNN, b2)
|
276
|
+
xDL = l2 - l1
|
277
|
+
xDC = xC2 - xC1
|
278
|
+
if (xC1 * xC2) == 0
|
279
|
+
xDH = 0
|
280
|
+
else
|
281
|
+
xNN = (xH2 - xH1).round(12)
|
282
|
+
if xNN.abs <= 180
|
283
|
+
xDH = xH2 - xH1
|
284
|
+
else
|
285
|
+
if xNN > 180
|
286
|
+
xDH = xH2 - xH1 - 360
|
287
|
+
else
|
288
|
+
xDH = xH2 - xH1 + 360
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
xDH = 2 * Math.sqrt(xC1 * xC2) * Math.sin(deg_to_rad(xDH / 2.0))
|
293
|
+
xLX = (l1 + l2) / 2.0
|
294
|
+
xCY = (xC1 + xC2) / 2.0
|
295
|
+
if xC1 * xC2 == 0
|
296
|
+
xHX = xH1 + xH2
|
297
|
+
else
|
298
|
+
xNN = (xH1 - xH2).round(12).abs
|
299
|
+
if xNN > 180
|
300
|
+
if xH2 + xH1 < 360
|
301
|
+
xHX = xH1 + xH2 + 360
|
302
|
+
else
|
303
|
+
xHX = xH1 + xH2 - 360
|
304
|
+
end
|
305
|
+
else
|
306
|
+
xHX = xH1 + xH2
|
307
|
+
end
|
308
|
+
xHX /= 2.0
|
309
|
+
end
|
310
|
+
xTX = 1 - 0.17 * Math.cos(deg_to_rad(xHX - 30)) + 0.24 *
|
311
|
+
Math.cos(deg_to_rad(2 * xHX)) + 0.32 *
|
312
|
+
Math.cos(deg_to_rad(3 * xHX + 6)) - 0.20 *
|
313
|
+
Math.cos(deg_to_rad(4 * xHX - 63 ))
|
314
|
+
xPH = 30 * Math.exp(-((xHX - 275) / 25.0) * ((xHX - 275) / 25.0))
|
315
|
+
xRC = 2 * Math.sqrt(xCY**7 / (xCY**7 + 25**7))
|
316
|
+
xSL = 1 + ((0.015 * ((xLX - 50) * (xLX - 50))) /
|
317
|
+
Math.sqrt(20 + ((xLX - 50) * (xLX - 50))))
|
318
|
+
xSC = 1 + 0.045 * xCY
|
319
|
+
xSH = 1 + 0.015 * xCY * xTX
|
320
|
+
xRT = -Math.sin(deg_to_rad(2 * xPH)) * xRC
|
321
|
+
xDL = xDL / (kl * xSL)
|
322
|
+
xDC = xDC / (kc * xSC)
|
323
|
+
xDH = xDH / (kh * xSH)
|
324
|
+
|
325
|
+
Math.sqrt(xDL**2 + xDC**2 + xDH**2 + xRT * xDC * xDH)
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|