67bd32cf97859e06a6855fee7d77a4f086f4e608
[nit.git] / lib / gamnit / tileset.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
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 # Support for `TileSet`, `TileSetFont` and drawing text with `TextSprites`
16 module tileset
17
18 import flat
19
20 # Efficiently retrieve tiles in a big texture
21 class TileSet
22 # Texture containing the tileset
23 var texture: Texture
24
25 # Width of a tile
26 var width: Numeric
27
28 # Height of a tile
29 var height: Numeric
30
31 # Number of columns of tiles in the texture
32 var nb_cols: Int = (texture.width / width.to_f).to_i is lazy
33
34 # Number of rows of tiles in the texture
35 var nb_rows: Int = (texture.height / height.to_f).to_i is lazy
36
37 # Cache of the subtextures of tiles
38 var subtextures: Array[Texture] is lazy do
39 var subtextures = new Array[Texture]
40 for j in [0..nb_rows[ do
41 for i in [0..nb_cols[ do
42 subtextures.add texture.subtexture(
43 i.to_f*width.to_f, j.to_f*height.to_f, width.to_f, height.to_f)
44 end
45 end
46 return subtextures
47 end
48
49 # Subtexture for the tile at `x, y`
50 #
51 # Require: `x < nb_cols and y < nb_rows`
52 fun [](x,y: Int): Texture
53 do
54 assert x >= 0 and x < nb_cols and y >= 0 and y <= nb_rows else print "{x}x{y}<?{nb_cols}x{nb_rows}"
55 var idx = x + y * nb_cols
56 return subtextures[idx]
57 end
58 end
59
60 # A monospace bitmap font where glyphs are stored in a tileset
61 class TileSetFont
62 super TileSet
63
64 # Set the characters present in `texture`
65 #
66 # Last all characters from left to right, then top to bottom.
67 # Line skip `\n`, are ignored and space ' ' skips holes in the tileset.
68 fun chars=(chars: Text)
69 is autoinit do
70 chars_cleaned = chars.replace("\n", "")
71 end
72
73 # Character present in the texture, set by `chars=`
74 private var chars_cleaned: Text is noautoinit
75
76 # Additional space to insert horizontally between characters
77 #
78 # A negative value may display overlapped tiles.
79 var hspace: Numeric = 0.0 is writable
80
81 # Additional space to insert vertically between characters
82 #
83 # A negative value may display overlapped tiles.
84 var vspace: Numeric = 0.0 is writable
85
86 # The glyph/tile/texture associated to `char`
87 #
88 # Returns null if `char` is not in `chars`.
89 fun char(char: Char): nullable Texture
90 do
91 var i = chars_cleaned.index_of(char)
92 if i == -1 then return null
93 return subtextures[i]
94 end
95
96 # Distance between the beginning of a letter tile and the beginning of the next letter tile
97 fun advance: Float do return width.to_f + hspace.to_f
98
99 # Distance between the beginning and the end of the longest line of `text`
100 fun text_width(text: Text): Numeric
101 do
102 var lines = text.split('\n')
103 if lines.is_empty then return 0
104
105 var longest = 0
106 for line in lines do longest = longest.max(line.length)
107
108 return longest.mul(advance)
109 end
110
111 # Distance between the top of the first line to the bottom of the last line in `text`
112 fun text_height(text: Text): Numeric
113 do
114 if text.is_empty then return 0
115
116 var n_lines = text.chars.count('\n')
117 return (n_lines+1).mul(height.add(vspace)).sub(vspace)
118 end
119 end
120
121 # Manage a set of sprites to display some text
122 class TextSprites
123
124 # Font used to draw text
125 var font: TileSetFont
126
127 # Top left of the first character in UI coordinates
128 var anchor: Point3d[Float]
129
130 # Last set of sprites generated to display `text=`
131 var sprites = new Array[Sprite]
132
133 # Sprite set where to put created sprites
134 #
135 # Defaults to `app::ui_sprites`, but it could also be set to a
136 # `app::sprites` or a custom collection.
137 var target_sprite_set: Set[Sprite] = app.ui_sprites is lazy, writable
138
139 private var cached_text: nullable Text = ""
140
141 # Last text drawn
142 fun text: nullable Text do return cached_text
143
144 # Update the text displayed by inserting new sprites into `app.ui_sprites`
145 #
146 # Does not redraw if `text` has not changed.
147 fun text=(text: nullable Text)
148 is autoinit do
149 # Don't redraw if text hasn't changed
150 if text == cached_text then return
151 cached_text = text
152
153 # Clean up last used sprites
154 for s in sprites do if target_sprite_set.has(s) then target_sprite_set.remove s
155 sprites.clear
156
157 if text == null then return
158
159 # Build new sprites
160 var dx = font.vspace.to_f/2.0
161 var dy = font.hspace.to_f/2.0
162 for c in text do
163 if c == '\n' then
164 dy -= font.height.to_f + font.vspace.to_f
165 dx = font.vspace.to_f/2.0
166 continue
167 else if c.is_whitespace then
168 dx += font.advance
169 continue
170 end
171
172 var tex = font.char(c)
173 if tex == null then
174 # Try to fallback to '?'
175 tex = font.char('?')
176 if tex == null then continue
177 end
178
179 sprites.add new Sprite(tex, anchor.offset(dx, dy, 0))
180 dx += font.advance
181 end
182
183 # Register sprites to be drawn by `app.ui_camera`
184 target_sprite_set.add_all sprites
185 end
186 end