gui 0.0.1.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +202 -0
- data/README.md +10 -0
- data/lib/gui.rb +26 -0
- data/lib/gui/context.rb +130 -0
- data/lib/gui/geom.rb +228 -0
- data/lib/gui/selector.rb +66 -0
- data/lib/gui/selector/checks.rb +178 -0
- data/lib/gui/texture.rb +103 -0
- data/lib/gui/version.rb +36 -0
- data/lib/gui/view.rb +134 -0
- data/lib/gui/window.rb +149 -0
- metadata +157 -0
data/lib/gui/selector.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# selector.rb
|
18
|
+
# Selector chain class.
|
19
|
+
|
20
|
+
|
21
|
+
module GUI
|
22
|
+
|
23
|
+
class Selector
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def view_matches_attr(view, name, value)
|
27
|
+
view.respond_to?(name) && view.__send__(name) == value
|
28
|
+
end
|
29
|
+
|
30
|
+
def build(selector_str)
|
31
|
+
|
32
|
+
end
|
33
|
+
end # singleton_class
|
34
|
+
|
35
|
+
# The next
|
36
|
+
attr_accessor :succ
|
37
|
+
# Array of proc/lambda objects that receive a view and return true if
|
38
|
+
# the view matches, otherwise nil/false
|
39
|
+
attr_accessor :attributes
|
40
|
+
attr_accessor :direct
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@succ = nil
|
44
|
+
@attributes = []
|
45
|
+
@direct = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Whether this selector matches a view.
|
49
|
+
def matches?(view)
|
50
|
+
attributes.empty? || attributes.all? { |sel_attr| sel_attr[view] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_match(view)
|
54
|
+
# TODO: Grab leaves and test selectors in reverse order
|
55
|
+
further = direct ? nil : self
|
56
|
+
if matches?(view)
|
57
|
+
return view unless @succ
|
58
|
+
further = @succ
|
59
|
+
end
|
60
|
+
|
61
|
+
view.subviews.detect { |subview| further.find_match(subview) }
|
62
|
+
end
|
63
|
+
|
64
|
+
end # Selector
|
65
|
+
|
66
|
+
end # GUI
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# checks.rb
|
18
|
+
# Selector checks (class/tag/attribute)
|
19
|
+
|
20
|
+
|
21
|
+
module GUI
|
22
|
+
|
23
|
+
#
|
24
|
+
# All selector checks (held by the attributes array of a selector) expect a
|
25
|
+
# call(view) method to be implemented. This returns either true or false values,
|
26
|
+
# though it's only necessary that they return things that evaluate to true or
|
27
|
+
# false. It is possible, therefore, to hand-write all your selectors rather than
|
28
|
+
# compiling them, which can be _much_ faster when performing lookups since
|
29
|
+
# attribute checks don't have to depend on reducing something by sending
|
30
|
+
# messages over and over.
|
31
|
+
#
|
32
|
+
# In practice, however, selectors are just slow in all my tests right now and I
|
33
|
+
# need to rewrite them to test starting with leaf views anyway (this should
|
34
|
+
# result in more specific matches and avoid recursive tests since it's then
|
35
|
+
# possible to iterate down through parents and cut off if a minimum depth isn't
|
36
|
+
# met [i.e., a selector with N views to match requires at least depth N, but
|
37
|
+
# can match views across depths greater than N if it has indirect matches]).
|
38
|
+
#
|
39
|
+
|
40
|
+
|
41
|
+
class ViewTagCheck
|
42
|
+
|
43
|
+
def initialize(tagname)
|
44
|
+
@tagname = tagname
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(view)
|
48
|
+
view.tag == tagname
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :[], :call
|
52
|
+
|
53
|
+
end # ViewTagCheck
|
54
|
+
|
55
|
+
|
56
|
+
class ViewClassCheck
|
57
|
+
|
58
|
+
def initialize(classname)
|
59
|
+
@classnames =
|
60
|
+
case classname
|
61
|
+
when Array then classname.dup
|
62
|
+
when Symbol then [classname]
|
63
|
+
when String then [classname.to_sym]
|
64
|
+
else raise ArgumentError, "Invalid class name type: #{classname.class}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(view)
|
69
|
+
klass = view.class
|
70
|
+
name = nil
|
71
|
+
while klass
|
72
|
+
name = ViewAttrCheck.extract_class_name(klass)
|
73
|
+
return true if @classnames.include?(name)
|
74
|
+
klass = klass.superclass
|
75
|
+
end
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
alias_method :[], :call
|
80
|
+
|
81
|
+
end # ViewClassCheck
|
82
|
+
|
83
|
+
|
84
|
+
class ViewAttrCheck
|
85
|
+
|
86
|
+
SCO_MARKER = '::'
|
87
|
+
KEYPATH_SEPARATOR = '.'
|
88
|
+
|
89
|
+
class << self
|
90
|
+
attr_accessor :__module_name_cache__
|
91
|
+
|
92
|
+
def extract_class_name(klass)
|
93
|
+
(__module_name_cache__ ||= {})[klass] ||= begin
|
94
|
+
# Cache classname symbols because string ops are slow
|
95
|
+
name = klass.name
|
96
|
+
sco_index = klass.rindex(SCO_MARKER)
|
97
|
+
if sco_index
|
98
|
+
klass.slice!(0 .. sco_index + 1)
|
99
|
+
end
|
100
|
+
name.to_sym
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end # singleton_class
|
104
|
+
|
105
|
+
def initialize(key, operator, operand)
|
106
|
+
@key = key.split(KEYPATH_SEPARATOR).map!(&:to_sym)
|
107
|
+
@operator = operator
|
108
|
+
@operand = operand
|
109
|
+
@is_string = @operand.kind_of?(String)
|
110
|
+
end
|
111
|
+
|
112
|
+
# NOTE: Deprecate and remove class checks for ViewAttrCheck? Might be a good
|
113
|
+
# idea, but it sort of remains since it's occasionally handy to do something
|
114
|
+
# like [content_view.class = Something]. Probably just going to remove this,
|
115
|
+
# though.
|
116
|
+
def class_check(klass)
|
117
|
+
# Cache Symbol for operand so I'm not converting it every time.
|
118
|
+
name = (@operand_sym ||= @operand.to_sym)
|
119
|
+
|
120
|
+
while klass
|
121
|
+
case @operator
|
122
|
+
when :equal
|
123
|
+
return true if self.class.extract_class_name(klass) == name
|
124
|
+
when :not_equal
|
125
|
+
return true unless self.class.extract_class_name(klass) == name
|
126
|
+
when :trueish # Necessarily true for classes.
|
127
|
+
true
|
128
|
+
else # Otherwise no test passes.
|
129
|
+
false
|
130
|
+
end
|
131
|
+
klass = klass.superclass
|
132
|
+
end
|
133
|
+
false
|
134
|
+
end
|
135
|
+
|
136
|
+
def call(view)
|
137
|
+
view_value = @key.reduce(view) { |value, msg| value.__send__(msg) }
|
138
|
+
|
139
|
+
if @is_string && !view_value.kind_of?(String)
|
140
|
+
view_value =
|
141
|
+
case view_value
|
142
|
+
when Class then return class_check(view_value) # return early
|
143
|
+
when Module then extract_class_name(view_value)
|
144
|
+
when Enumerable then
|
145
|
+
# There is a case here where doing something like
|
146
|
+
# `included_modules <- X` will fail because the values contained by
|
147
|
+
# the Enumerable are modules but the check won't know, so it'll just
|
148
|
+
# always fail. Thought about working around this by mapping modules
|
149
|
+
# to their extracted names, but decided I'll only do that if it turns
|
150
|
+
# out to be a problem.
|
151
|
+
return false unless @operator == :contains
|
152
|
+
view_value
|
153
|
+
else view_value.to_s
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
case operator
|
158
|
+
when :trueish then !!view_value
|
159
|
+
when :falseish then !view_value
|
160
|
+
when :equal then view_value == @operand
|
161
|
+
when :not_equal then view_value != @operand
|
162
|
+
when :greater then view_value > @operand
|
163
|
+
when :greater_equal then view_value >= @operand
|
164
|
+
when :lesser then view_value < @operand
|
165
|
+
when :lesser_equal then view_value <= @operand
|
166
|
+
when :contains
|
167
|
+
view_value.respond_to?(:include?) && view_value.include?(@operand)
|
168
|
+
else
|
169
|
+
raise SelectorError, "Invalid operator for ViewAttrCheck: #{operator}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :[], :call
|
174
|
+
|
175
|
+
end # ViewAttrCheck
|
176
|
+
|
177
|
+
end # GUI
|
178
|
+
|
data/lib/gui/texture.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# texture.rb
|
18
|
+
# Wrapper around GL texture names.
|
19
|
+
|
20
|
+
|
21
|
+
require 'stb-image'
|
22
|
+
require 'snow-data'
|
23
|
+
|
24
|
+
|
25
|
+
module GUI
|
26
|
+
|
27
|
+
class Texture
|
28
|
+
|
29
|
+
TextureName = Snow::CStruct.new { uint32_t :name }
|
30
|
+
|
31
|
+
LOAD_TEXTURE_BLOCK = -> (data, x, y, components) do
|
32
|
+
internalFormat =
|
33
|
+
case components
|
34
|
+
when STBI::COMPONENTS_GREY then Gl::GL_RED
|
35
|
+
when STBI::COMPONENTS_GREY_ALPHA then Gl::GL_RG
|
36
|
+
when STBI::COMPONENTS_RGB then Gl::GL_RGB
|
37
|
+
when STBI::COMPONENTS_RGB_ALPHA then Gl::GL_RGBA
|
38
|
+
else raise ArgumentError, "Invalid number of texture components"
|
39
|
+
end
|
40
|
+
|
41
|
+
name = TextureName.new
|
42
|
+
raise "Unable to allocate texture name" unless name
|
43
|
+
|
44
|
+
Gl::glGetIntegerv(Gl::GL_TEXTURE_BINDING_2D, name.address)
|
45
|
+
prev_name = name.name
|
46
|
+
|
47
|
+
Gl::glGenTextures(1, name.address)
|
48
|
+
Gl::glBindTexture(Gl::GL_TEXTURE_2D, name.name)
|
49
|
+
|
50
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_WRAP_S, Gl::GL_CLAMP_TO_EDGE)
|
51
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_WRAP_T, Gl::GL_CLAMP_TO_EDGE)
|
52
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_MIN_FILTER, Gl::GL_LINEAR)
|
53
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_MAG_FILTER, Gl::GL_LINEAR)
|
54
|
+
|
55
|
+
Gl::glTexImage2D(
|
56
|
+
Gl::GL_TEXTURE_2D, # target
|
57
|
+
0, # level
|
58
|
+
format, # internal format
|
59
|
+
x, y, # width, height
|
60
|
+
0, # border
|
61
|
+
format, # format
|
62
|
+
Gl::GL_UNSIGNED_BYTE, # typep
|
63
|
+
data
|
64
|
+
)
|
65
|
+
|
66
|
+
glBindTexture(Gl::GL_TEXTURE_2D, prev_name)
|
67
|
+
|
68
|
+
name
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Allocates a new texture using the given IO object.
|
73
|
+
# If a block is given, the texture is only valid in the scope of the block
|
74
|
+
# unless retained elsewhere.
|
75
|
+
def initialize(io, &block)
|
76
|
+
@name = STBI.load_image(io, STBI::COMPONENTS_DEFAULT, &LOAD_TEXTURE_BLOCK)
|
77
|
+
@refs = 0
|
78
|
+
retain(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def retain
|
82
|
+
@refs += 1
|
83
|
+
|
84
|
+
if block_given?
|
85
|
+
yield self
|
86
|
+
release
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def release
|
91
|
+
@refs -= 1
|
92
|
+
if @refs == 0
|
93
|
+
# If it's necessary to do anything else to release the object, pass it
|
94
|
+
# to a block first.
|
95
|
+
yield self if block_given?
|
96
|
+
|
97
|
+
glDeleteTextures(1, @name.address)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end # Texture
|
102
|
+
|
103
|
+
end # GUI
|
data/lib/gui/version.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# version.rb
|
18
|
+
# Version information for the gui gem.
|
19
|
+
|
20
|
+
|
21
|
+
module GUI
|
22
|
+
|
23
|
+
GUI_VERSION = '0.0.1.pre1'.freeze
|
24
|
+
GUI_LICENSE_BRIEF = 'Apache 2.0 License'.freeze
|
25
|
+
GUI_GEM_ROOT = File.expand_path('../../../', __FILE__).freeze
|
26
|
+
|
27
|
+
# Don't load the license unless it's needed.
|
28
|
+
define_singleton_method(:GUI_LICENSE_FULL, &-> do
|
29
|
+
File.open("#{GUI_GEM_ROOT}/COPYING") do |io|
|
30
|
+
io.read
|
31
|
+
end.freeze.tap do |license_txt|
|
32
|
+
::GUI::set_const(:GUI_LICENSE_FULL, license_txt)
|
33
|
+
end
|
34
|
+
end)
|
35
|
+
|
36
|
+
end # GUI
|
data/lib/gui/view.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# widget.rb
|
18
|
+
# Base class for view types.
|
19
|
+
|
20
|
+
|
21
|
+
require 'gui/geom'
|
22
|
+
|
23
|
+
|
24
|
+
module GUI
|
25
|
+
|
26
|
+
class View
|
27
|
+
|
28
|
+
# View tag (default: nil)
|
29
|
+
attr_accessor :tag
|
30
|
+
|
31
|
+
# Subviews held by the view. Should not be modified directly. Instead, to
|
32
|
+
# add a subview, use add_view.
|
33
|
+
attr_reader :subviews
|
34
|
+
|
35
|
+
# Rectangular portion
|
36
|
+
attr_accessor :frame # Rect
|
37
|
+
|
38
|
+
def initialize(frame = nil)
|
39
|
+
@needs_layout = false
|
40
|
+
@invalidated = nil
|
41
|
+
@subviews = []
|
42
|
+
@attributes = []
|
43
|
+
@superview = nil
|
44
|
+
@tag = nil
|
45
|
+
@frame = frame || Rect.new
|
46
|
+
|
47
|
+
invalidate
|
48
|
+
request_layout
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the containing superview of the view.
|
52
|
+
def superview
|
53
|
+
@superview
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the containing superview of the view. This invalidates and requests
|
57
|
+
# layout on the previous superview, if any.
|
58
|
+
def superview=(new_superview)
|
59
|
+
old_superview = @superview
|
60
|
+
if !old_superview.nil?
|
61
|
+
old_superview.delete(self)
|
62
|
+
old_superview.invalidate(@frame.dup)
|
63
|
+
old_superview.request_layout
|
64
|
+
end
|
65
|
+
|
66
|
+
@superview = new_superview
|
67
|
+
if !new_superview.nil?
|
68
|
+
new_superview.children << self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_view(view)
|
73
|
+
raise ArgumentError, "View already has a superview" if view.superview
|
74
|
+
view.superview = self
|
75
|
+
end
|
76
|
+
|
77
|
+
def bounds
|
78
|
+
@frame.with_origin(0, 0)
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove_from_superview
|
82
|
+
self.superview = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def invalidated_region
|
86
|
+
@invalidated
|
87
|
+
end
|
88
|
+
|
89
|
+
def invalidate(region = nil)
|
90
|
+
if @invalidated
|
91
|
+
@invalidated.contains_both!(region || @frame)
|
92
|
+
else
|
93
|
+
@invalidated = (region || @frame.with_origin(0, 0)).dup
|
94
|
+
end.intersection!(bounds)
|
95
|
+
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def request_layout
|
100
|
+
@needs_layout = true
|
101
|
+
end
|
102
|
+
|
103
|
+
def needs_layout?
|
104
|
+
@needs_layout
|
105
|
+
end
|
106
|
+
|
107
|
+
def perform_layout
|
108
|
+
end
|
109
|
+
|
110
|
+
def view_with_tag(tag)
|
111
|
+
if @tag == tag
|
112
|
+
self
|
113
|
+
else
|
114
|
+
@subviews.detect { |subview| subview.view_with_tag(tag) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def view_with_selector(selector)
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def [](selector)
|
123
|
+
case selector
|
124
|
+
when Symbol then view_with_tag(selector)
|
125
|
+
when Selector then view_with_selector(selector)
|
126
|
+
when String then view_with_selector(Selector.build(selector))
|
127
|
+
when Numeric then @subviews[selector]
|
128
|
+
else raise ArgumentError, "Invalid selector for View#[]"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end # View
|
133
|
+
|
134
|
+
end # GUI
|