├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── shard.yml ├── spec ├── boleite_spec.cr └── spec_helper.cr └── src ├── boleite.cr └── boleite ├── application.cr ├── backend ├── assimp.cr ├── backend.cr ├── configuration.cr ├── freeimage.cr ├── glfw_backend.cr ├── glfw_input.cr ├── glfw_input_translators.cr ├── glfw_monitor.cr ├── glfw_opengl_context.cr ├── glfw_surface.cr ├── graphics │ ├── opengl_frame_buffer.cr │ ├── opengl_shader.cr │ ├── opengl_texture.cr │ └── opengl_vertex_buffer.cr ├── graphics_context.cr ├── opengl_helper.cr ├── truetype.cr └── video_mode.cr ├── clock.cr ├── graphics ├── camera.cr ├── camera_2d.cr ├── camera_3d.cr ├── circle_shape.cr ├── drawable.cr ├── font.cr ├── font │ ├── glyph.cr │ ├── page.cr │ └── row.cr ├── forward_renderer.cr ├── frame_buffer.cr ├── image.cr ├── line_shape.cr ├── partial_circle_shape.cr ├── render_target.cr ├── renderer.cr ├── shader.cr ├── shader_parser.cr ├── shape.cr ├── shape │ ├── shape.shader │ └── vertices.cr ├── sprite.cr ├── sprite │ ├── sprite.shader │ └── vertices.cr ├── text.cr ├── text │ ├── format_rule.cr │ ├── formatter.cr │ ├── text.shader │ └── vertices.cr ├── texture.cr ├── transformable.cr └── vertex_buffer.cr ├── gui ├── button.cr ├── container.cr ├── default_design │ ├── button.cr │ ├── cache.cr │ ├── container.cr │ ├── design.cr │ ├── desktop.cr │ ├── image.cr │ ├── input_field.cr │ ├── label.cr │ ├── layout.cr │ ├── text_box.cr │ └── window.cr ├── design.cr ├── desktop.cr ├── graphics.cr ├── grid_layout.cr ├── gui.cr ├── helpers.cr ├── image.cr ├── input │ ├── container.cr │ ├── gui.cr │ ├── handler.cr │ ├── input_field.cr │ ├── widget.cr │ └── window.cr ├── input_field.cr ├── label.cr ├── layout.cr ├── paste.shader ├── renderer.cr ├── root.cr ├── text_box.cr ├── widget.cr └── window.cr ├── input ├── enums.cr ├── input_action.cr ├── input_event.cr ├── input_processor.cr ├── input_receiver.cr └── input_router.cr ├── math ├── color.cr ├── matrix.cr ├── matrix_imp.cr ├── rect.cr ├── vector.cr └── vector_imp.cr ├── random.cr ├── serializable.cr ├── serializer.cr ├── serializers ├── backend │ ├── configuration.cr │ └── video_mode.cr ├── random.cr ├── vector.cr └── version.cr ├── state.cr ├── state_stack.cr └── version.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | 6 | # Libraries don't need dependency lock 7 | # Dependencies will be locked in application that uses them 8 | /shard.lock 9 | 10 | # Editor Specific Ignores 11 | .editorconfig 12 | /.vscode/ 13 | builder.sh -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Henrik Valter Vogelius Hansson 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 6 | 7 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 8 | 9 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 10 | 11 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boleite 2 | 3 | ![Boleite](http://i.imgur.com/BKTwCEH.png) 4 | 5 | 6 | Work In Progress Framework for developing Games in Crystal. You can view it in use at my other project [Ego](https://github.com/Groogy/ego). 7 | 8 | ## Installation 9 | 10 | Add this to your application's `shard.yml`: 11 | 12 | ```yaml 13 | dependencies: 14 | boleite: 15 | github: Groogy/boleite 16 | ``` 17 | 18 | You will also need to install libraries needed for the backend to work. As long as they are in PATH it will work just fine, if you are using a package manager you probably don't need to worry about it. Needed dependencies are: 19 | 20 | * libfreetype-6 21 | * libfreeimage-3 22 | * libGL 23 | * libglfw-3 24 | 25 | ## Usage 26 | 27 | To get everything setup and started you need to create an Application and a state to push onto it's StateStack. The application is also responsible for finding the configuration the framework will be using or defining it's default values. The configuration needs to define the backend configuration data so the frameworks backend can be properly setup. 28 | 29 | ```crystal 30 | require "boleite" 31 | 32 | class MyConfiguration < Boleite::Configuration 33 | property :backend 34 | 35 | @backend = Boleite::BackendConfiguration.new 36 | 37 | struct Serializer 38 | def marshal(obj, node) 39 | node.marshal "backend", obj.backend 40 | end 41 | 42 | def unmarshal(node) 43 | config = MyConfiguration.new 44 | config.backend = node.unmarshal "backend", Boleite::BackendConfiguration 45 | config 46 | end 47 | end 48 | 49 | extend Boleite::Serializable(Serializer) 50 | end 51 | 52 | class MyApplication < Boleite::Application 53 | CONFIGURATION_FILE = "config.yml" 54 | 55 | def create_configuration : Boleite::Configuration 56 | if File.exists? CONFIGURATION_FILE 57 | File.open(CONFIGURATION_FILE, "r") do |file| 58 | serializer = Boleite::Serializer.new nil 59 | serializer.read(file) 60 | config = serializer.unmarshal(MyConfiguration) 61 | config.as(MyConfiguration) 62 | end 63 | else 64 | File.open(CONFIGURATION_FILE, "w") do |file| 65 | config = MyConfiguration.new 66 | config.backend = @backend.default_config 67 | serializer = Boleite::Serializer.new nil 68 | serializer.marshal(config) 69 | serializer.dump(file) 70 | config 71 | end 72 | end 73 | end 74 | end 75 | 76 | SHADER_STR = "#version 330 77 | vertex 78 | { 79 | layout(location = 0) in vec4 pos; 80 | 81 | void main() 82 | { 83 | gl_Position = pos; 84 | } 85 | } 86 | fragment 87 | { 88 | layout(location = 0) out vec4 outputAlbedo; 89 | 90 | void main() 91 | { 92 | outputAlbedo = vec4(1, 0, 0, 1); 93 | } 94 | }" 95 | 96 | class MyState < Boleite::State 97 | def initialize(@app : MyApplication) 98 | super() 99 | 100 | gfx = @app.graphics 101 | target = gfx.main_target 102 | shader = Boleite::Shader.load_string SHADER_STR, gfx 103 | @camera2d = Boleite::Camera2D.new(target.width.to_f32, target.height.to_f32, 0f32, 1f32) 104 | @renderer = Boleite::ForwardRenderer.new gfx, @camera2d, shader 105 | end 106 | 107 | def enable 108 | end 109 | 110 | def disable 111 | end 112 | 113 | def update(delta) 114 | end 115 | 116 | def render(delta) 117 | @renderer.clear Boleite::Color.black 118 | @renderer.present 119 | end 120 | end 121 | 122 | app = MyApplication.new 123 | app.state_stack.push MyState.new(app) 124 | app.run 125 | ``` 126 | 127 | ## Development 128 | 129 | TODO: Write development instructions here 130 | 131 | ## Contributing 132 | 133 | 1. Fork it ( https://github.com/Groogy/boleite/fork ) 134 | 2. Create your feature branch (git checkout -b my-new-feature) 135 | 3. Commit your changes (git commit -am 'Add some feature') 136 | 4. Push to the branch (git push origin my-new-feature) 137 | 5. Create a new Pull Request 138 | 139 | ## Contributors 140 | 141 | - [Groogy](https://github.com/Groogy) Henrik Valter Vogelius Hansson - creator, maintainer 142 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: boleite 2 | version: 0.1.0 3 | 4 | authors: 5 | - Henrik Valter Vogelius Hansson 6 | 7 | dependencies: 8 | crystal-clear: 9 | github: Groogy/crystal-clear 10 | lib_glfw3: 11 | github: Groogy/crystal_lib_glfw3 12 | lib_gl: 13 | github: jellymann/crystal_lib_gl 14 | cute: 15 | github: Papierkorb/cute 16 | 17 | libraries: 18 | libfreetype: ~> 6 19 | libfreeimage: ~> 3 20 | 21 | crystal: 1.0.0 22 | 23 | license: zlib 24 | -------------------------------------------------------------------------------- /spec/boleite_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Boleite do 4 | # TODO: Write tests 5 | 6 | it "works" do 7 | false.should eq(true) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/boleite" 3 | -------------------------------------------------------------------------------- /src/boleite.cr: -------------------------------------------------------------------------------- 1 | require "crystal-clear" 2 | require "lib_glfw3" 3 | require "lib_gl" 4 | require "cute" 5 | require "./boleite/math/*" 6 | require "./boleite/backend/*" 7 | require "./boleite/graphics/*" 8 | require "./boleite/backend/graphics/*" 9 | require "./boleite/input/*" 10 | require "./boleite/gui/helpers.cr" 11 | require "./boleite/gui/*" 12 | require "./boleite/gui/input/*" 13 | require "./boleite/*" 14 | require "./boleite/serializers/*" 15 | require "./boleite/serializers/backend/*" 16 | 17 | module Boleite 18 | VERSION = Version.new(0, 1, 0) 19 | end 20 | -------------------------------------------------------------------------------- /src/boleite/application.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Configuration 2 | abstract def backend : BackendConfiguration 3 | end 4 | 5 | abstract class Boleite::Application 6 | class InputHandler < InputReceiver 7 | def initialize() 8 | end 9 | 10 | def bind(app) 11 | register ClosedAction, ->app.close 12 | end 13 | end 14 | 15 | getter :configuration 16 | getter :input_router 17 | getter :state_stack 18 | getter :graphics 19 | 20 | @backend : Backend 21 | @configuration : Configuration 22 | @graphics : GraphicsContext 23 | @input_handler = InputHandler.new 24 | @input_router = InputRouter.new 25 | @state_stack = StateStack.new 26 | @clock = Clock.new 27 | @running = true 28 | 29 | def initialize 30 | @backend = Backend.create_glfw 31 | @configuration = create_configuration 32 | @graphics = @backend.create_graphics(@configuration.backend) 33 | @input_handler.bind(self) 34 | @input_router.register(@input_handler) 35 | end 36 | 37 | def run 38 | @clock.restart 39 | while @running 40 | tick_time = @clock.restart 41 | top_state = @state_stack.top 42 | process_events 43 | process_state top_state, tick_time 44 | end 45 | end 46 | 47 | def close 48 | @running = false 49 | end 50 | 51 | abstract def create_configuration : Configuration 52 | 53 | private def process_events 54 | while event = @backend.poll_event 55 | @input_router.process event 56 | end 57 | end 58 | 59 | private def process_state(state, delta) 60 | state.update(delta) 61 | state.render(delta) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /src/boleite/backend/assimp.cr: -------------------------------------------------------------------------------- 1 | @[Link("assimp")] 2 | lib LibAssImp 3 | MAXLEN = 1024 4 | MAX_NUMBER_OF_COLOR_SETS = 0x8 5 | MAX_NUMBER_OF_TEXTURECOORDS = 0x8 6 | HINTMAXTEXTURELEN = 9 7 | 8 | type Real = Float32 9 | type Int = Int32 10 | type UInt = UInt32 11 | 12 | struct String 13 | length : LibC::ULong 14 | char : LibC::Char[MAXLEN] 15 | end 16 | 17 | enum MetadataType 18 | BOOL = 0 19 | INT32 = 1 20 | UINT64 = 2 21 | FLOAT = 3 22 | DOUBLE = 4 23 | AISTRING = 5 24 | AIVECTOR3D = 6 25 | META_MAX = 7 26 | end 27 | 28 | enum PrimitiveType 29 | POINT = 0x1 30 | LINE = 0x2 31 | TRIANGLE = 0x4 32 | POLYGON = 0x8 33 | end 34 | 35 | enum MorphingMethod 36 | VERTEX_BLEND = 0x1 37 | MORPH_NORMALIZED = 0x2 38 | MORPH_RELATIVE = 0x3 39 | end 40 | 41 | enum TextureOp 42 | Multiply = 0x0 43 | Add = 0x1 44 | Subtract = 0x2 45 | Divide = 0x3 46 | SmoothAdd = 0x4 47 | SignedAdd = 0x5 48 | end 49 | 50 | enum TextureMapMode 51 | Wrap = 0x0 52 | Clamp = 0x1 53 | Decal = 0x3 54 | Mirror = 0x2 55 | end 56 | 57 | enum TextureMapping 58 | UV = 0x0 59 | SPHERE = 0x1 60 | CYLINDER = 0x2 61 | BOX = 0x3 62 | PLANE = 0x4 63 | OTHER = 0x5 64 | end 65 | 66 | enum TextureType 67 | NONE = 0x0 68 | DIFFUSE = 0x1 69 | SPECULAR = 0x2 70 | AMBIENT = 0x3 71 | EMISSIVE = 0x4 72 | HEIGHT = 0x5 73 | NORMALS = 0x6 74 | SHININESS = 0x7 75 | OPACITY = 0x8 76 | DISPLACEMENT = 0x9 77 | LIGHTMAP = 0xA 78 | REFLECTION = 0xB 79 | UNKNOWN = 0xC 80 | end 81 | 82 | enum ShadingMode 83 | Flat = 0x1 84 | Gouraud = 0x2 85 | Phong = 0x3 86 | Blinn = 0x4 87 | Toon = 0x5 88 | OrenNayar = 0x6 89 | Minnaert = 0x7 90 | Cooktorrance = 0x8 91 | NoShading = 0x9 92 | Fresnel = 0xA 93 | end 94 | 95 | enum TextureFlags 96 | Invert = 0x1 97 | UseAlpha = 0x2 98 | IgnoreAlpha = 0x4 99 | end 100 | 101 | enum BlendMode 102 | Default = 0x0 103 | Additive = 0x1 104 | end 105 | 106 | enum PropertyTypeInfo 107 | Float = 0x1 108 | Double = 0x2 109 | String = 0x3 110 | Integer = 0x4 111 | Buffer = 0x5 112 | end 113 | 114 | enum AnimBehaviour 115 | DEFAULT = 0x0 116 | CONSTANT = 0x1 117 | LINEAR = 0x2 118 | REPEAT = 0x3 119 | end 120 | 121 | enum LightSourceType 122 | UNDEFINED = 0x0 123 | DIRECTIONAL = 0x1 124 | POINT = 0x2 125 | SPOT = 0x3 126 | AMBIENT = 0x4 127 | AREA = 0x5 128 | end 129 | 130 | struct MetadataEntry 131 | type : MetadataType 132 | data : Void* 133 | end 134 | 135 | struct Metadata 136 | num_properties : LibC::UInt 137 | keys : String* 138 | values : MetadataEntry* 139 | end 140 | 141 | struct Matrix4x4 142 | a : Real[4] 143 | b : Real[4] 144 | c : Real[4] 145 | d : Real[4] 146 | end 147 | 148 | struct Quaternion 149 | w : Real 150 | x : Real 151 | y : Real 152 | z : Real 153 | end 154 | 155 | struct Vector2D 156 | x : Real 157 | y : Real 158 | end 159 | 160 | struct Vector3D 161 | x : Real 162 | y : Real 163 | z : Real 164 | end 165 | 166 | struct Color3D 167 | r : Real 168 | g : Real 169 | b : Real 170 | end 171 | 172 | struct Color4D 173 | r : Real 174 | g : Real 175 | b : Real 176 | a : Real 177 | end 178 | 179 | struct Texel 180 | b : LibC::UChar 181 | g : LibC::UChar 182 | r : LibC::UChar 183 | a : LibC::UChar 184 | end 185 | 186 | struct Texture 187 | width : LibC::UInt 188 | height : LibC::UInt 189 | format_hint : LibC::Char[HINTMAXTEXTURELEN] 190 | data : Texel* 191 | filename : String 192 | end 193 | 194 | struct Light 195 | name : String 196 | type : LightSourceType 197 | position : Vector3D 198 | direction : Vector3D 199 | up : Vector3D 200 | attenuation_constant : LibC::Float 201 | attenuation_linear : LibC::Float 202 | attenuation_quadratic : LibC::Float 203 | color_diffuse : Color3D 204 | color_specular : Color3D 205 | color_ambient : Color3D 206 | angle_inner_cone : LibC::Float 207 | angle_outer_cone : LibC::Float 208 | size : Vector2D 209 | end 210 | 211 | struct Camera 212 | name : String 213 | position : Vector3D 214 | up : Vector3D 215 | look_at : Vector3D 216 | horizontal_fov : LibC::Float 217 | clip_plane_near : LibC::Float 218 | clip_plane_far : LibC::Float 219 | aspect : LibC::Float 220 | end 221 | 222 | struct UVTransform 223 | translation : Vector2D 224 | scaling : Vector2D 225 | rotation : Real 226 | end 227 | 228 | struct MaterialProperty 229 | key : String 230 | semantic : LibC::UInt 231 | index : LibC::UInt 232 | data_length : LibC::UInt 233 | type : PropertyTypeInfo 234 | data : LibC::Char 235 | end 236 | 237 | struct Material 238 | properties : MaterialProperty** 239 | num_properties : LibC::UInt 240 | num_allocated : LibC::UInt 241 | end 242 | 243 | struct VectorKey 244 | time : LibC::Double 245 | value : Vector3D 246 | end 247 | 248 | struct QuatKey 249 | time : LibC::Double 250 | value : Quaternion 251 | end 252 | 253 | struct MeshKey 254 | time : LibC::Double 255 | value : LibC::UInt 256 | end 257 | 258 | struct MeshMorphKey 259 | time : LibC::Double 260 | values : LibC::UInt* 261 | weights : LibC::Double* 262 | num_values_and_weights : LibC::UInt 263 | end 264 | 265 | struct NodeAnim 266 | node_name : String 267 | num_position_keys : LibC::UInt 268 | position_keys : VectorKey* 269 | num_rotation_keys : LibC::UInt 270 | rotation_keys : QuatKey* 271 | num_scaling_keys : LibC::UInt 272 | scaling_keys : VectorKey* 273 | pre_state : AnimBehaviour 274 | post_state : AnimBehaviour 275 | end 276 | 277 | struct MeshAnim 278 | name : String 279 | num_keys : LibC::UInt 280 | keys : MeshKey* 281 | end 282 | 283 | struct MeshMorphAnim 284 | name : String 285 | num_keys : LibC::UInt 286 | keys : MeshMorphKey* 287 | end 288 | 289 | struct Animation 290 | name : String 291 | duration : LibC::Double 292 | ticks_per_second : LibC::Double 293 | num_channels : LibC::UInt 294 | channels : NodeAnim** 295 | num_mesh_channels : LibC::UInt 296 | mesh_channels : MeshAnim** 297 | num_morph_mesh_channels : LibC::UInt 298 | morph_mesh_channels : MeshMorphAnim** 299 | end 300 | 301 | struct Face 302 | num_indices : LibC::UInt 303 | indices : LibC::UInt* 304 | end 305 | 306 | struct VertexWeight 307 | vertex_id : LibC::UInt 308 | weight : LibC::Float 309 | end 310 | 311 | struct Bone 312 | name : String 313 | num_weights : LibC::UInt 314 | weights : VertexWeight* 315 | offset_matrix : Matrix4x4 316 | end 317 | 318 | struct AnimMesh 319 | name : String 320 | vertices : Vector3D* 321 | normals : Vector3D* 322 | tangents : Vector3D* 323 | bitangents : Vector3D* 324 | colors : Color4D*[MAX_NUMBER_OF_COLOR_SETS] 325 | texture_coords : Vector3D*[MAX_NUMBER_OF_TEXTURECOORDS] 326 | num_vertices : LibC::UInt 327 | weight : LibC::Float 328 | end 329 | 330 | struct Mesh 331 | primitive_types : LibC::UInt 332 | num_vertices : LibC::UInt 333 | num_faces : LibC::UInt 334 | vertices : Vector3D* 335 | normals : Vector3D* 336 | tangents : Vector3D* 337 | bitangents : Vector3D* 338 | colors : Color4D*[MAX_NUMBER_OF_COLOR_SETS] 339 | texture_coords : Vector3D*[MAX_NUMBER_OF_TEXTURECOORDS] 340 | num_uv_components : LibC::UInt[MAX_NUMBER_OF_TEXTURECOORDS] 341 | faces : Face* 342 | num_bones : LibC::UInt 343 | bones : Bone** 344 | material_index : LibC::UInt 345 | name : String 346 | num_anim_meshes : LibC::UInt 347 | anim_meshes : AnimMesh** 348 | method : LibC::UInt 349 | end 350 | 351 | struct Node 352 | name : String 353 | transformation : Matrix4x4 354 | parent : Node* 355 | num_children : LibC::UInt 356 | children : Node** 357 | num_meshes : LibC::UInt 358 | meshes : LibC::UInt* 359 | metadata : Metadata* 360 | end 361 | 362 | struct Scene 363 | flags : LibC::UInt 364 | root_node : Node* 365 | num_meshes : LibC::UInt 366 | meshes : Mesh** 367 | num_materials : LibC::UInt 368 | materials : Material** 369 | num_animations : LibC::UInt 370 | animations : Animation** 371 | num_textures : LibC::UInt 372 | textures : Texture** 373 | num_lights : LibC::UInt 374 | lights : Light** 375 | num_cameras : LibC::UInt 376 | cameras : Camera** 377 | metadata : Metadata* 378 | end 379 | 380 | @[Flags] 381 | enum Process : UInt32 382 | CalcTangentSpace 383 | JoinIdenticalVertices 384 | MakeLeftHanded 385 | Triangulate 386 | RemoveComponent 387 | GenNormals 388 | GenSmoothNormals 389 | SplitLargeMeshes 390 | PreTransformVertices 391 | LimitBoneWeights 392 | ValidateDataStructure 393 | ImproveCacheLocality 394 | RemoveRedundantMaterials 395 | FixInfacingNormals 396 | SortByPType 397 | FindDegenerates 398 | FindInvalidData 399 | GenUVCoords 400 | TransformUVCoords 401 | FindInstances 402 | OptimizeMeshes 403 | OptimizeGraph 404 | FlipUVs 405 | FlipWindingOrder 406 | SplitByBoneCount 407 | Debone 408 | end 409 | 410 | fun import_file = aiImportFile(file : LibC::Char*, flags : UInt32) : Scene* 411 | fun release_import = aiReleaseImport( scene : Scene* ) : Void 412 | 413 | fun get_error_string = aiGetErrorString() : LibC::Char* 414 | end -------------------------------------------------------------------------------- /src/boleite/backend/backend.cr: -------------------------------------------------------------------------------- 1 | class Boleite:: BackendException < Exception 2 | end 3 | 4 | abstract class Boleite::Backend 5 | def self.create_glfw() 6 | Private::GLFW.new 7 | end 8 | 9 | abstract def create_graphics(config : BackendConfiguration) : GraphicsContext 10 | abstract def default_config : BackendConfiguration 11 | abstract def poll_event : InputEvent | Nil 12 | end 13 | -------------------------------------------------------------------------------- /src/boleite/backend/configuration.cr: -------------------------------------------------------------------------------- 1 | class Boleite::BackendConfiguration 2 | enum GfxType 3 | OpenGL 4 | Vulkan # Not supported 5 | end 6 | 7 | property :gfx 8 | property :version 9 | property :video_mode 10 | property :double_buffering 11 | property :multisamples 12 | 13 | @gfx = GfxType::OpenGL 14 | @version = Version.new(4, 5) 15 | @video_mode = VideoMode.new 16 | @double_buffering = true 17 | @multisamples = 2_u8 18 | 19 | def initialize() 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/boleite/backend/freeimage.cr: -------------------------------------------------------------------------------- 1 | @[Link("freeimage")] 2 | lib LibFreeImage 3 | type FIBITMAP = Void 4 | alias BYTE = UInt8 5 | 6 | enum FORMAT 7 | FIF_UNKNOWN = -1 8 | FIF_BMP = 0 9 | FIF_ICO = 1 10 | FIF_JPEG = 2 11 | FIF_JNG = 3 12 | FIF_KOALA = 4 13 | FIF_LBM = 5 14 | FIF_IFF = FIF_LBM 15 | FIF_MNG = 6 16 | FIF_PBM = 7 17 | FIF_PBMRAW = 8 18 | FIF_PCD = 9 19 | FIF_PCX = 10 20 | FIF_PGM = 11 21 | FIF_PGMRAW = 12 22 | FIF_PNG = 13 23 | FIF_PPM = 14 24 | FIF_PPMRAW = 15 25 | FIF_RAS = 16 26 | FIF_TARGA = 17 27 | FIF_TIFF = 18 28 | FIF_WBMP = 19 29 | FIF_PSD = 20 30 | FIF_CUT = 21 31 | FIF_XBM = 22 32 | FIF_XPM = 23 33 | FIF_DDS = 24 34 | FIF_GIF = 25 35 | FIF_HDR = 26 36 | FIF_FAXG3 = 27 37 | FIF_SGI = 28 38 | FIF_EXR = 29 39 | FIF_J2K = 30 40 | FIF_JP2 = 31 41 | FIF_PFM = 32 42 | FIF_PICT = 33 43 | FIF_RAW = 34 44 | FIF_WEBP = 35 45 | FIF_JXR = 36 46 | end 47 | 48 | {% if flag?(:osx) %} 49 | RGBA_RED = 0 50 | RGBA_GREEN = 1 51 | RGBA_BLUE = 2 52 | RGBA_ALPHA = 3 53 | {% else %} 54 | RGBA_RED = 2 55 | RGBA_GREEN = 1 56 | RGBA_BLUE = 0 57 | RGBA_ALPHA = 3 58 | {% end %} 59 | 60 | fun allocate = FreeImage_Allocate(width : Int32, height : Int32, bpp : Int32, red_mask : UInt32, green_mask : UInt32, blue_mask : UInt32) : FIBITMAP* 61 | fun load = FreeImage_Load(fif : FORMAT, filename : LibC::Char*, flags : Int32) : FIBITMAP* 62 | fun clone = FreeImage_Clone(dib : FIBITMAP*) : FIBITMAP* 63 | fun unload = FreeImage_Unload(dib : FIBITMAP*) : Void 64 | 65 | fun getFileType = FreeImage_GetFileType(filename : LibC::Char*, size : Int32) : FORMAT 66 | 67 | fun getWidth = FreeImage_GetWidth(dib : FIBITMAP*) : UInt32 68 | fun getHeight = FreeImage_GetHeight(dib : FIBITMAP*) : UInt32 69 | fun getBPP = FreeImage_GetBPP(dib : FIBITMAP*) : UInt32 70 | fun getLine = FreeImage_GetLine(dib : FIBITMAP*) : UInt32 71 | fun getPitch = FreeImage_GetPitch(dib : FIBITMAP*) : UInt32 72 | fun getBits = FreeImage_GetBits(dib : FIBITMAP*) : BYTE* 73 | end 74 | -------------------------------------------------------------------------------- /src/boleite/backend/glfw_backend.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Private::GLFW < Boleite::Backend 2 | include CrystalClear 3 | 4 | enum ErrorCode 5 | NotInitialized = LibGLFW3::NOT_INITIALIZED 6 | NoCurrentContext = LibGLFW3::NO_CURRENT_CONTEXT 7 | InvalidEnum = LibGLFW3::INVALID_ENUM 8 | OutOfMemory = LibGLFW3::OUT_OF_MEMORY 9 | APIUnavailable = LibGLFW3::API_UNAVAILABLE 10 | VersionUnavailable = LibGLFW3::VERSION_UNAVAILABLE 11 | PlatformError = LibGLFW3::PLATFORM_ERROR 12 | FormatUnavailable = LibGLFW3::FORMAT_UNAVAILABLE 13 | end 14 | 15 | struct ErrorData 16 | property :code, description 17 | 18 | def initialize(@code : ErrorCode, @description : String) 19 | end 20 | end 21 | 22 | @@errors = [] of ErrorData 23 | 24 | def initialize() 25 | @initialized = LibGLFW3.init != 0 26 | LibGLFW3.setErrorCallback(->GLFW.on_error) if @initialized 27 | 28 | @primary_surface = nil 29 | @primary_monitor = GLFWMonitor.new safe_call { LibGLFW3.getPrimaryMonitor } 30 | end 31 | 32 | def finalize() 33 | if @initialized 34 | @initialized = false 35 | unless @primary_surface.nil? 36 | @primary_surface.as(GLFWSurface).finalize 37 | end 38 | LibGLFW3.terminate 39 | end 40 | end 41 | 42 | def is_initialized? 43 | @initialized 44 | end 45 | 46 | requires config.gfx == BackendConfiguration::GfxType::OpenGL 47 | def create_graphics(config : BackendConfiguration) : GraphicsContext 48 | setup_main_target_settings config 49 | native = create_surface config.video_mode 50 | GLFWInput.bind_callbacks native 51 | @primary_surface = GLFWSurface.new native 52 | create_graphics_context @primary_surface, config 53 | end 54 | 55 | def default_config : BackendConfiguration 56 | config = BackendConfiguration.new 57 | config.gfx = BackendConfiguration::GfxType::OpenGL 58 | config.version = Version.new(4, 5) 59 | config.video_mode = default_video_mode(VideoMode::Mode::Borderless) 60 | config 61 | end 62 | 63 | def poll_event : InputEvent | Nil 64 | GLFWInput.poll 65 | end 66 | 67 | def default_video_mode(mode) 68 | current = @primary_monitor.current_video_mode 69 | VideoMode.new(current.width.to_u, current.height.to_u, mode, current.refreshRate.to_u16) 70 | end 71 | 72 | private def safe_call 73 | GLFW.safe_call { yield } 74 | end 75 | 76 | def self.safe_call 77 | check_errors 78 | val = yield 79 | check_errors 80 | return val 81 | end 82 | 83 | def self.check_errors 84 | if error = @@errors.pop? 85 | raise BackendException.new("#{error.code}: #{error.description}") 86 | end 87 | end 88 | 89 | protected def self.on_error(error : Int32, description : Int8*) 90 | @@errors << ErrorData.new(ErrorCode.new(error), String.new(description.as(UInt8*))) 91 | end 92 | 93 | private def create_graphics_context(surface, config : BackendConfiguration) : GraphicsContext 94 | case config.gfx 95 | when BackendConfiguration::GfxType::OpenGL 96 | create_opengl_context surface, config 97 | when BackendConfiguration::GfxType::Vulkan 98 | create_vulkan_context surface, config 99 | else 100 | raise BackendException.new("Unknown requested graphics context! Given #{config.gfx}.") 101 | end 102 | end 103 | 104 | private def create_opengl_context(surface, config : BackendConfiguration) 105 | GLFWOpenGLContext.new surface.as(GLFWSurface) 106 | end 107 | 108 | private def create_vulkan_context(surface, config : BackendConfiguration) 109 | raise BackendException.new("Vulkan support has not yet been implemented!") 110 | end 111 | 112 | private def setup_main_target_settings(config : BackendConfiguration) 113 | setup_opengl_settings(config) 114 | setup_refresh_rate(config) 115 | setup_rendering_settings(config) 116 | setup_window_settings(config) 117 | end 118 | 119 | private def setup_opengl_settings(config : BackendConfiguration) 120 | safe_call do 121 | LibGLFW3.windowHint(LibGLFW3::OPENGL_PROFILE, LibGLFW3::OPENGL_CORE_PROFILE) 122 | LibGLFW3.windowHint(LibGLFW3::OPENGL_FORWARD_COMPAT, 1) 123 | LibGLFW3.windowHint(LibGLFW3::CONTEXT_VERSION_MAJOR, config.version.major) 124 | LibGLFW3.windowHint(LibGLFW3::CONTEXT_VERSION_MINOR, config.version.minor) 125 | {% if flag?(:debug) %} 126 | LibGLFW3.windowHint(LibGLFW3::OPENGL_DEBUG_CONTEXT, 1) 127 | {% end %} 128 | end 129 | end 130 | 131 | private def setup_refresh_rate(config : BackendConfiguration) 132 | safe_call do 133 | if config.video_mode.any_refresh_rate? 134 | LibGLFW3.windowHint(LibGLFW3::REFRESH_RATE, config.video_mode.refresh_rate) 135 | else 136 | LibGLFW3.windowHint(LibGLFW3::REFRESH_RATE, LibGLFW3::DONT_CARE) 137 | end 138 | end 139 | end 140 | 141 | private def setup_rendering_settings(config) 142 | safe_call do 143 | if config.double_buffering 144 | LibGLFW3.windowHint(LibGLFW3::DOUBLEBUFFER, 1) 145 | else 146 | LibGLFW3.windowHint(LibGLFW3::DOUBLEBUFFER, 0) 147 | end 148 | LibGLFW3.windowHint(LibGLFW3::SAMPLES, config.multisamples) 149 | end 150 | end 151 | 152 | private def setup_window_settings(config) 153 | safe_call do 154 | LibGLFW3.windowHint(LibGLFW3::RESIZABLE, 0) 155 | case config.video_mode.mode 156 | when VideoMode::Mode::Windowed 157 | LibGLFW3.windowHint(LibGLFW3::DECORATED, 1) 158 | when VideoMode::Mode::Fullscreen 159 | when VideoMode::Mode::Borderless 160 | LibGLFW3.windowHint(LibGLFW3::DECORATED, 0) 161 | config.video_mode = default_video_mode(VideoMode::Mode::Borderless) 162 | end 163 | end 164 | end 165 | 166 | private def create_surface(video_mode) 167 | safe_call do 168 | monitor = video_mode.mode.fullscreen? ? @primary_monitor.ptr : Pointer(Void).null.as(LibGLFW3::Monitor) 169 | surface = LibGLFW3.createWindow(video_mode.resolution.x, video_mode.resolution.y, "Hello Crystal!", monitor, nil) 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /src/boleite/backend/glfw_input.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Private::GLFWInput 2 | @@events = [] of InputEvent 3 | @@iterator = 0 4 | 5 | def self.update 6 | if @@iterator >= @@events.size 7 | @@events.clear 8 | @@iterator = 0 9 | GLFW.safe_call{ LibGLFW3.pollEvents } 10 | end 11 | end 12 | 13 | def self.poll : InputEvent | Nil 14 | self.update 15 | if @@iterator < @@events.size 16 | event = @@events[@@iterator] 17 | @@iterator += 1 18 | event 19 | else 20 | nil 21 | end 22 | end 23 | 24 | def self.bind_callbacks(window) 25 | GLFW.safe_call do 26 | LibGLFW3.setWindowCloseCallback window, ->on_window_close 27 | LibGLFW3.setKeyCallback window, ->on_key 28 | LibGLFW3.setCharCallback window, ->on_char 29 | LibGLFW3.setMouseButtonCallback window, ->on_mouse_button 30 | LibGLFW3.setScrollCallback window, ->on_scroll 31 | LibGLFW3.setCursorPosCallback window, ->on_cursor_pos 32 | end 33 | end 34 | 35 | def self.on_window_close(window) 36 | @@events << ClosedEvent.new 37 | end 38 | 39 | def self.on_key(window, key, scancode, action, mods) 40 | key = translate_key key 41 | action = translate_action action 42 | mods = translate_mods mods 43 | @@events << KeyEvent.new key, action, mods 44 | end 45 | 46 | def self.on_char(window, key) 47 | @@events << CharEvent.new key 48 | end 49 | 50 | def self.on_mouse_button(window, button, action, mods) 51 | button = translate_mouse_button button 52 | action = translate_action action 53 | mods = translate_mods mods 54 | @@events << MouseButtonEvent.new button, action, mods 55 | end 56 | 57 | def self.on_scroll(window, x_scroll, y_scroll) 58 | @@events << MouseScrollEvent.new x_scroll, y_scroll 59 | end 60 | 61 | def self.on_cursor_pos(window, x_pos, y_pos) 62 | @@events << MousePosEvent.new x_pos, y_pos 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /src/boleite/backend/glfw_input_translators.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Private::GLFWInput 2 | def self.translate_key(key) 3 | return case key 4 | when LibGLFW3::KEY_UNKNOWN; Key::Unknown 5 | when LibGLFW3::KEY_SPACE; Key::Space 6 | when LibGLFW3::KEY_APOSTROPHE; Key::Apostrophe 7 | when LibGLFW3::KEY_COMMA; Key::Comma 8 | when LibGLFW3::KEY_MINUS; Key::Minus 9 | when LibGLFW3::KEY_PERIOD; Key::Period 10 | when LibGLFW3::KEY_SLASH; Key::Slash 11 | when LibGLFW3::KEY_0; Key::Num0 12 | when LibGLFW3::KEY_1; Key::Num1 13 | when LibGLFW3::KEY_2; Key::Num2 14 | when LibGLFW3::KEY_3; Key::Num3 15 | when LibGLFW3::KEY_4; Key::Num4 16 | when LibGLFW3::KEY_5; Key::Num5 17 | when LibGLFW3::KEY_6; Key::Num6 18 | when LibGLFW3::KEY_7; Key::Num7 19 | when LibGLFW3::KEY_8; Key::Num8 20 | when LibGLFW3::KEY_9; Key::Num9 21 | when LibGLFW3::KEY_SEMICOLON; Key::Semicolon 22 | when LibGLFW3::KEY_EQUAL; Key::Equal 23 | when LibGLFW3::KEY_A; Key::A 24 | when LibGLFW3::KEY_B; Key::B 25 | when LibGLFW3::KEY_C; Key::C 26 | when LibGLFW3::KEY_D; Key::D 27 | when LibGLFW3::KEY_E; Key::E 28 | when LibGLFW3::KEY_F; Key::F 29 | when LibGLFW3::KEY_G; Key::G 30 | when LibGLFW3::KEY_H; Key::H 31 | when LibGLFW3::KEY_I; Key::I 32 | when LibGLFW3::KEY_J; Key::J 33 | when LibGLFW3::KEY_K; Key::K 34 | when LibGLFW3::KEY_L; Key::L 35 | when LibGLFW3::KEY_M; Key::M 36 | when LibGLFW3::KEY_N; Key::N 37 | when LibGLFW3::KEY_O; Key::O 38 | when LibGLFW3::KEY_P; Key::P 39 | when LibGLFW3::KEY_Q; Key::Q 40 | when LibGLFW3::KEY_R; Key::R 41 | when LibGLFW3::KEY_S; Key::S 42 | when LibGLFW3::KEY_T; Key::T 43 | when LibGLFW3::KEY_U; Key::U 44 | when LibGLFW3::KEY_V; Key::V 45 | when LibGLFW3::KEY_W; Key::W 46 | when LibGLFW3::KEY_X; Key::X 47 | when LibGLFW3::KEY_Y; Key::Y 48 | when LibGLFW3::KEY_Z; Key::Z 49 | when LibGLFW3::KEY_LEFT_BRACKET; Key::LeftBracket 50 | when LibGLFW3::KEY_BACKSLASH; Key::Backslash 51 | when LibGLFW3::KEY_RIGHT_BRACKET; Key::RightBracket 52 | when LibGLFW3::KEY_GRAVE_ACCENT; Key::GraveAccent 53 | when LibGLFW3::KEY_WORLD_1; Key::World1 54 | when LibGLFW3::KEY_WORLD_2; Key::World2 55 | when LibGLFW3::KEY_ESCAPE; Key::Escape 56 | when LibGLFW3::KEY_ENTER; Key::Enter 57 | when LibGLFW3::KEY_TAB; Key::Tab 58 | when LibGLFW3::KEY_BACKSPACE; Key::Backspace 59 | when LibGLFW3::KEY_INSERT; Key::Insert 60 | when LibGLFW3::KEY_DELETE; Key::Delete 61 | when LibGLFW3::KEY_RIGHT; Key::Right 62 | when LibGLFW3::KEY_LEFT; Key::Left 63 | when LibGLFW3::KEY_DOWN; Key::Down 64 | when LibGLFW3::KEY_UP; Key::Up 65 | when LibGLFW3::KEY_PAGE_UP; Key::PageUp 66 | when LibGLFW3::KEY_PAGE_DOWN; Key::PageDown 67 | when LibGLFW3::KEY_HOME; Key::Home 68 | when LibGLFW3::KEY_END; Key::End 69 | when LibGLFW3::KEY_CAPS_LOCK; Key::CapsLock 70 | when LibGLFW3::KEY_SCROLL_LOCK; Key::ScrollLock 71 | when LibGLFW3::KEY_NUM_LOCK; Key::NumLock 72 | when LibGLFW3::KEY_PRINT_SCREEN; Key::PrintScreen 73 | when LibGLFW3::KEY_PAUSE; Key::Pause 74 | when LibGLFW3::KEY_F1; Key::F1 75 | when LibGLFW3::KEY_F2; Key::F2 76 | when LibGLFW3::KEY_F3; Key::F3 77 | when LibGLFW3::KEY_F4; Key::F4 78 | when LibGLFW3::KEY_F5; Key::F5 79 | when LibGLFW3::KEY_F6; Key::F6 80 | when LibGLFW3::KEY_F7; Key::F7 81 | when LibGLFW3::KEY_F8; Key::F8 82 | when LibGLFW3::KEY_F9; Key::F9 83 | when LibGLFW3::KEY_F10; Key::F10 84 | when LibGLFW3::KEY_F11; Key::F11 85 | when LibGLFW3::KEY_F12; Key::F12 86 | when LibGLFW3::KEY_F13; Key::F13 87 | when LibGLFW3::KEY_F14; Key::F14 88 | when LibGLFW3::KEY_F15; Key::F15 89 | when LibGLFW3::KEY_F16; Key::F16 90 | when LibGLFW3::KEY_F17; Key::F17 91 | when LibGLFW3::KEY_F18; Key::F18 92 | when LibGLFW3::KEY_F19; Key::F19 93 | when LibGLFW3::KEY_F20; Key::F20 94 | when LibGLFW3::KEY_F21; Key::F21 95 | when LibGLFW3::KEY_F22; Key::F22 96 | when LibGLFW3::KEY_F23; Key::F23 97 | when LibGLFW3::KEY_F24; Key::F24 98 | when LibGLFW3::KEY_F25; Key::F25 99 | when LibGLFW3::KEY_KP_0; Key::KeyPad0 100 | when LibGLFW3::KEY_KP_1; Key::KeyPad1 101 | when LibGLFW3::KEY_KP_2; Key::KeyPad2 102 | when LibGLFW3::KEY_KP_3; Key::KeyPad3 103 | when LibGLFW3::KEY_KP_4; Key::KeyPad4 104 | when LibGLFW3::KEY_KP_5; Key::KeyPad5 105 | when LibGLFW3::KEY_KP_6; Key::KeyPad6 106 | when LibGLFW3::KEY_KP_7; Key::KeyPad7 107 | when LibGLFW3::KEY_KP_8; Key::KeyPad8 108 | when LibGLFW3::KEY_KP_9; Key::KeyPad9 109 | when LibGLFW3::KEY_KP_DECIMAL; Key::KeyPadDecimal 110 | when LibGLFW3::KEY_KP_DIVIDE; Key::KeyPadDivide 111 | when LibGLFW3::KEY_KP_MULTIPLY; Key::KeyPadMultiply 112 | when LibGLFW3::KEY_KP_SUBTRACT; Key::KeyPadSubtract 113 | when LibGLFW3::KEY_KP_ADD; Key::KeyPadAdd 114 | when LibGLFW3::KEY_KP_ENTER; Key::KeyPadEnter 115 | when LibGLFW3::KEY_KP_EQUAL; Key::KeyPadEqual 116 | when LibGLFW3::KEY_LEFT_SHIFT; Key::LeftShift 117 | when LibGLFW3::KEY_LEFT_CONTROL; Key::LeftControl 118 | when LibGLFW3::KEY_LEFT_ALT; Key::LeftAlt 119 | when LibGLFW3::KEY_LEFT_SUPER; Key::LeftSuper 120 | when LibGLFW3::KEY_RIGHT_SHIFT; Key::RightShift 121 | when LibGLFW3::KEY_RIGHT_CONTROL; Key::RightControl 122 | when LibGLFW3::KEY_RIGHT_ALT; Key::RightAlt 123 | when LibGLFW3::KEY_RIGHT_SUPER; Key::RightSuper 124 | when LibGLFW3::KEY_MENU; Key::Menu 125 | else; Key::Unknown 126 | end 127 | end 128 | 129 | def self.translate_mouse_button(button) 130 | case button 131 | when LibGLFW3::MOUSE_BUTTON_1; Mouse::Button1 132 | when LibGLFW3::MOUSE_BUTTON_2; Mouse::Button2 133 | when LibGLFW3::MOUSE_BUTTON_3; Mouse::Button3 134 | when LibGLFW3::MOUSE_BUTTON_4; Mouse::Button4 135 | when LibGLFW3::MOUSE_BUTTON_5; Mouse::Button5 136 | when LibGLFW3::MOUSE_BUTTON_6; Mouse::Button6 137 | when LibGLFW3::MOUSE_BUTTON_7; Mouse::Button7 138 | when LibGLFW3::MOUSE_BUTTON_8; Mouse::Button8 139 | when LibGLFW3::MOUSE_BUTTON_LAST; Mouse::Last 140 | when LibGLFW3::MOUSE_BUTTON_LEFT; Mouse::Left 141 | when LibGLFW3::MOUSE_BUTTON_RIGHT; Mouse::Right 142 | when LibGLFW3::MOUSE_BUTTON_MIDDLE; Mouse::Middle 143 | else; Mouse::Unknown 144 | end 145 | end 146 | 147 | def self.translate_action(action) 148 | case action 149 | when LibGLFW3::RELEASE; InputAction::Release 150 | when LibGLFW3::PRESS; InputAction::Press 151 | when LibGLFW3::REPEAT; InputAction::Repeat 152 | else; InputAction::Unknown 153 | end 154 | end 155 | 156 | def self.translate_mods(mods) 157 | KeyMod.new(mods) 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /src/boleite/backend/glfw_monitor.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Private::GLFWMonitor 2 | def initialize(@ptr : LibGLFW3::Monitor) 3 | end 4 | 5 | def current_video_mode 6 | LibGLFW3.getVideoMode(@ptr).value 7 | end 8 | 9 | def ptr 10 | @ptr 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/boleite/backend/glfw_opengl_context.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::GraphicsContext 2 | end 3 | 4 | class Boleite::Private::GLFWOpenGLContext < Boleite::GraphicsContext 5 | def initialize(@glfw_surface : GLFWSurface) 6 | GLFW.safe_call { LibGLFW3.makeContextCurrent(@glfw_surface.ptr) } 7 | GLFW.safe_call { LibGLFW3.swapInterval 0 } 8 | GL.safe_call { LibGL.cullFace LibGL::BACK } 9 | GL.safe_call { LibGL.enable LibGL::CULL_FACE } 10 | end 11 | 12 | def main_target : Boleite::RenderTarget 13 | @glfw_surface 14 | end 15 | 16 | def clear(color) : Void 17 | GL.safe_call { LibGL.clearColor(color.r, color.g, color.b, color.a) } 18 | GL.safe_call { LibGL.clear LibGL::COLOR_BUFFER_BIT } 19 | end 20 | 21 | def clear_depth : Void 22 | GL.safe_call { LibGL.clear LibGL::DEPTH_BUFFER_BIT } 23 | end 24 | 25 | def scissor=(rect : IntRect) : Void 26 | rect.left = {rect.left, 0}.max 27 | rect.top = {rect.top, 0}.max 28 | GL.safe_call { LibGL.enable LibGL::SCISSOR_TEST } 29 | GL.safe_call { LibGL.scissor rect.left, rect.top, rect.width, rect.height } 30 | end 31 | 32 | def scissor=(arg : Nil) : Void 33 | GL.safe_call { LibGL.disable LibGL::SCISSOR_TEST } 34 | end 35 | 36 | def present : Void 37 | GLFW.safe_call{ LibGLFW3.swapBuffers(@glfw_surface.ptr) } 38 | end 39 | 40 | def create_vertex_buffer_object : VertexBufferObject 41 | OpenGLVertexBufferObject.new 42 | end 43 | 44 | def create_vertex_buffer : VertexBuffer 45 | OpenGLVertexBuffer.new 46 | end 47 | 48 | def create_shader(parser : ShaderParser) : Shader 49 | OpenGLShader.new(parser) 50 | end 51 | 52 | def create_texture : Texture 53 | OpenGLTexture.new 54 | end 55 | 56 | def create_frame_buffer : FrameBuffer 57 | OpenGLFrameBuffer.new 58 | end 59 | 60 | def texture_maximum_size : UInt32 61 | OpenGLTexture.maximum_size 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /src/boleite/backend/glfw_surface.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::RenderTarget 2 | end 3 | 4 | class Boleite::Private::GLFWSurface < Boleite::RenderTarget 5 | def initialize(@surface : LibGLFW3::Window) 6 | end 7 | 8 | def finalize() 9 | unless @surface.null? 10 | GLFW.safe_call do 11 | LibGLFW3.destroyWindow(@surface) 12 | end 13 | @surface = Pointer(Void).null.as(LibGLFW3::Window) 14 | end 15 | end 16 | 17 | def width : UInt32 18 | size.x 19 | end 20 | 21 | def height : UInt32 22 | size.y 23 | end 24 | 25 | def size : Boleite::Vector2u 26 | GLFW.safe_call do 27 | LibGLFW3.getWindowSize @surface, out width, out height 28 | Vector2u.new width.to_u32, height.to_u32 29 | end 30 | end 31 | 32 | def ptr 33 | @surface 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /src/boleite/backend/graphics/opengl_frame_buffer.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Private::OpenGLFrameBuffer < Boleite::FrameBuffer 2 | include CrystalClear 3 | 4 | struct AttachmentData 5 | getter texture, slot 6 | 7 | def initialize(@texture : OpenGLTexture, @slot : UInt8) 8 | end 9 | end 10 | 11 | @object_id : LibGL::UInt = 0u32 12 | @attachments = {} of Symbol => AttachmentData 13 | @depth_texture : OpenGLTexture? = nil 14 | 15 | def initialize : Void 16 | GL.safe_call { LibGL.genFramebuffers 1, pointerof(@object_id) } 17 | end 18 | 19 | def finalize : Void 20 | GL.safe_call { LibGL.deleteFramebuffers 1, pointerof(@object_id) } 21 | end 22 | 23 | def activate(&block) 24 | activate LibGL::FRAMEBUFFER, &block 25 | end 26 | 27 | def activate(target, &block) 28 | GL.safe_call { LibGL.bindFramebuffer target, @object_id } 29 | result = yield 30 | GL.safe_call { LibGL.bindFramebuffer target, 0 } 31 | result 32 | end 33 | 34 | def blit(src, src_rect, dst_rect) : Void 35 | activate(LibGL::DRAW_FRAMEBUFFER) do 36 | src.activate(LibGL::READ_FRAMEBUFFER) do 37 | src1, src2 = src_rect.bounds 38 | dst1, dst2 = dst_rect.bounds 39 | GL.safe_call { LibGL.blitFramebuffer src1.x, src1.y, src2.x, src2.y, dst1.x, dst1.y, dst2.x, dst2.y, LibGL::COLOR_BUFFER_BIT, LibGL::NEAREST } 40 | end 41 | end 42 | end 43 | 44 | def attach_buffer(texture : Texture, identifier : Symbol, slot : UInt8) : Void 45 | activate do 46 | tex = texture.as(OpenGLTexture) 47 | GL.safe_call { LibGL.framebufferTexture2D LibGL::FRAMEBUFFER, LibGL::COLOR_ATTACHMENT0 + slot, LibGL::TEXTURE_2D, tex.identifier, 0 } 48 | @attachments[identifier] = AttachmentData.new tex, slot 49 | end 50 | end 51 | 52 | requires texture.is_depth? 53 | def attach_depth_buffer(texture) : Void 54 | activate do 55 | tex = texture.as(OpenGLTexture) 56 | GL.safe_call { LibGL.framebufferTexture2D LibGL::FRAMEBUFFER, LibGL::DEPTH_ATTACHMENT, LibGL::TEXTURE_2D, tex.identifier, 0 } 57 | @depth_texture = tex 58 | end 59 | end 60 | 61 | def detach_buffer(identifier) : Void 62 | activate do 63 | attachment = @attachments[identifier] 64 | GL.safe_call { LibGL.framebufferTexture2D Lib::FRAMEBUFFER, LibGL::COLOR_ATTACHMENT0 + attachment.slot, LibGL::TEXTURE_2D, 0, 0 } 65 | @attachments.delete identifier 66 | end 67 | end 68 | 69 | def detach_depth_buffer() : Void 70 | activate do 71 | GL.safe_call { LibGL.framebufferTexture2D Lib::FRAMEBUFFER, LibGL::DEPTH_ATTACHMENT, LibGL::TEXTURE_2D, 0, 0 } 72 | @depth_texture = nil 73 | end 74 | end 75 | 76 | def detach_all_buffers() : Void 77 | activate do 78 | @attachments.size.times do |index| 79 | GL.safe_call { LibGL.framebufferTexture2D Lib::FRAMEBUFFER, LibGL::COLOR_ATTACHMENT0 + index, LibGL::TEXTURE_2D, 0, 0 } 80 | end 81 | GL.safe_call { LibGL.framebufferTexture2D Lib::FRAMEBUFFER, LibGL::DEPTH_ATTACHMENT, LibGL::TEXTURE_2D, 0, 0 } 82 | @depth_texture = nil 83 | @attachments.clear 84 | end 85 | end 86 | end -------------------------------------------------------------------------------- /src/boleite/backend/graphics/opengl_shader.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Private::OpenGLShader < Boleite::Shader 2 | include CrystalClear 3 | 4 | @program_id = LibGL::UInt.zero 5 | @objects = [] of OpenGLShaderObject 6 | @depth_settings : ShaderDepthSettings 7 | @blend_settings : ShaderBlendSettings 8 | @value_settings : ShaderValueSettings 9 | @uniforms = {} of String => LibGL::Int 10 | @textures = {} of LibGL::Int => OpenGLTexture 11 | 12 | def initialize(parser : ShaderParser) 13 | @depth_settings = parser.depth_settings 14 | @blend_settings = parser.blend_settings 15 | @value_settings = parser.value_settings 16 | 17 | compile_objects parser 18 | link_shader 19 | end 20 | 21 | def finalize 22 | if @program_id > 0 23 | GL.safe_call { LibGL.deleteProgram @program_id } 24 | @program_id = LibGL::UInt.zero 25 | end 26 | end 27 | 28 | def activate(&block) 29 | activate(true, &block) 30 | end 31 | 32 | def activate(use_settings, &block) 33 | GL.safe_call { LibGL.useProgram @program_id } 34 | if use_settings 35 | apply_settings 36 | apply_textures 37 | end 38 | result = yield 39 | GL.safe_call{ LibGL.useProgram 0 } 40 | result 41 | end 42 | 43 | def set_parameter(name, value : Float32) : Void 44 | activate false do 45 | loc = uniform_location_for name 46 | GL.safe_call { LibGL.uniform1f loc, value } 47 | end 48 | end 49 | 50 | def set_parameter(name, value : Vector2f32) : Void 51 | activate false do 52 | loc = uniform_location_for name 53 | GL.safe_call { LibGL.uniform2f loc, value.x, value.y } 54 | end 55 | end 56 | 57 | def set_parameter(name, value : Vector3f32) : Void 58 | activate false do 59 | loc = uniform_location_for name 60 | GL.safe_call { LibGL.uniform3f loc, value.x, value.y, value.z } 61 | end 62 | end 63 | 64 | def set_parameter(name, value : Vector4f32) : Void 65 | activate false do 66 | loc = uniform_location_for name 67 | GL.safe_call { LibGL.uniform4f loc, value.x, value.y, value.z, value.w } 68 | end 69 | end 70 | 71 | def set_parameter(name, value : Matrix33f32 ) : Void 72 | activate false do 73 | loc = uniform_location_for name 74 | GL.safe_call { LibGL.uniformMatrix3fv loc, 1, LibGL::FALSE, value.elements } 75 | end 76 | end 77 | 78 | def set_parameter(name, value : Matrix44f32 ) : Void 79 | activate false do 80 | loc = uniform_location_for name 81 | GL.safe_call { LibGL.uniformMatrix4fv loc, 1, LibGL::FALSE, value.elements } 82 | end 83 | end 84 | 85 | def set_parameter(name, value : Texture) : Void 86 | activate false do 87 | loc = uniform_location_for name 88 | @textures[loc] = value.as(OpenGLTexture) 89 | end 90 | end 91 | 92 | def has_world_transform? : Bool 93 | @value_settings.world_transform.empty? == false 94 | end 95 | 96 | def has_view_transform? : Bool 97 | @value_settings.view_transform.empty? == false 98 | end 99 | 100 | def has_projection_transform? : Bool 101 | @value_settings.projection_transform.empty? == false 102 | end 103 | 104 | requires has_world_transform? 105 | def set_world_transform(value) : Void 106 | set_parameter @value_settings.world_transform, value 107 | end 108 | 109 | requires has_view_transform? 110 | def set_view_transform(value) : Void 111 | set_parameter @value_settings.view_transform, value 112 | end 113 | 114 | requires has_projection_transform? 115 | def set_projection_transform(value) : Void 116 | set_parameter @value_settings.projection_transform, value 117 | end 118 | 119 | def world_transform=(value) : Void 120 | set_world_transform(value) 121 | end 122 | 123 | def view_transform=(value) : Void 124 | set_view_transform(value) 125 | end 126 | 127 | def projection_transform=(value) : Void 128 | set_projection_transform(value) 129 | end 130 | 131 | private def uniform_location_for(name) : LibGL::Int 132 | loc = @uniforms[name]? 133 | if loc.nil? 134 | loc = GL.safe_call { LibGL.getUniformLocation @program_id, name.to_unsafe.as(Int8*) } 135 | @uniforms[name] = loc 136 | end 137 | loc 138 | end 139 | 140 | private def apply_settings 141 | GL.safe_call do 142 | if @depth_settings.enabled 143 | LibGL.enable LibGL::DEPTH_TEST 144 | LibGL.depthFunc self.class.translate_depth_func(@depth_settings.func) 145 | else 146 | LibGL.disable LibGL::DEPTH_TEST 147 | end 148 | if @blend_settings.enabled 149 | LibGL.enable LibGL::BLEND 150 | LibGL.blendFunc self.class.translate_blend_factor(@blend_settings.source_factor), 151 | self.class.translate_blend_factor(@blend_settings.destination_factor) 152 | LibGL.blendEquation self.class.translate_blend_func(@blend_settings.func) 153 | else 154 | LibGL.disable LibGL::BLEND 155 | end 156 | end 157 | end 158 | 159 | private def apply_textures 160 | slot = 1 161 | @textures.each do |loc, texture| 162 | GL.safe_call { LibGL.uniform1i loc, slot } 163 | GL.safe_call { LibGL.activeTexture LibGL::TEXTURE0 + slot } 164 | texture.bind 165 | slot += 1 166 | end 167 | GL.safe_call { LibGL.activeTexture LibGL::TEXTURE0 } 168 | end 169 | 170 | private def compile_objects(parser) 171 | ShaderType.each do |type| 172 | if type <= ShaderType::None || type >= ShaderType::Count 173 | next 174 | end 175 | 176 | if parser.has_shader type 177 | obj = OpenGLShaderObject.new parser.shader_source(type), type 178 | @objects << obj 179 | end 180 | end 181 | end 182 | 183 | private def link_shader 184 | finalize 185 | GL.safe_call do 186 | @program_id = LibGL.createProgram 187 | @objects.each { |obj| LibGL.attachShader @program_id, obj.gl_identifier } 188 | LibGL.linkProgram @program_id 189 | 190 | LibGL.getProgramiv @program_id, LibGL::LINK_STATUS, out status 191 | if status == LibGL::FALSE 192 | LibGL.getProgramiv @program_id, LibGL::INFO_LOG_LENGTH, out length 193 | info_log = Slice(LibGL::Char).new(length + 1) 194 | LibGL.getProgramInfoLog @program_id, length, nil, info_log 195 | message = String.new(info_log.to_unsafe.as(UInt8*).to_slice(length + 1)) 196 | raise ShaderException.new "Shader linker failure: " + message 197 | end 198 | end 199 | end 200 | 201 | def self.translate_depth_func(func : ShaderDepthSettings::Function) 202 | case func 203 | when ShaderDepthSettings::Function::Always; LibGL::ALWAYS 204 | when ShaderDepthSettings::Function::Never; LibGL::NEVER 205 | when ShaderDepthSettings::Function::Less; LibGL::LESS 206 | when ShaderDepthSettings::Function::Greater; LibGL::GREATER 207 | when ShaderDepthSettings::Function::LessEqual; LibGL::LEQUAL 208 | when ShaderDepthSettings::Function::GreaterEqual; LibGL::GEQUAL 209 | when ShaderDepthSettings::Function::Equal; LibGL::EQUAL 210 | when ShaderDepthSettings::Function::NotEqual; LibGL::NOTEQUAL 211 | else 212 | raise ArgumentError.new "Invalid depth function given!" 213 | end 214 | end 215 | 216 | def self.translate_blend_factor(factor : ShaderBlendSettings::Factor) 217 | case factor 218 | when ShaderBlendSettings::Factor::One; LibGL::ONE 219 | when ShaderBlendSettings::Factor::Zero; LibGL::ZERO 220 | when ShaderBlendSettings::Factor::Source; LibGL::SRC_COLOR 221 | when ShaderBlendSettings::Factor::OneMinusSource; LibGL::ONE_MINUS_SRC_COLOR 222 | when ShaderBlendSettings::Factor::Destination; LibGL::DST_COLOR 223 | when ShaderBlendSettings::Factor::OneMinusDestination; LibGL::ONE_MINUS_DST_COLOR 224 | when ShaderBlendSettings::Factor::SourceAlpha; LibGL::SRC_ALPHA 225 | when ShaderBlendSettings::Factor::OneMinusSourceAlpha; LibGL::ONE_MINUS_SRC_ALPHA 226 | when ShaderBlendSettings::Factor::DestinationAlpha; LibGL::DST_ALPHA 227 | when ShaderBlendSettings::Factor::OneMinusDestinationAlpha; LibGL::ONE_MINUS_DST_ALPHA 228 | when ShaderBlendSettings::Factor::Constant; LibGL::CONSTANT_COLOR 229 | when ShaderBlendSettings::Factor::OneMinusConstant; LibGL::ONE_MINUS_CONSTANT_COLOR 230 | when ShaderBlendSettings::Factor::ConstantAlpha; LibGL::CONSTANT_ALPHA 231 | when ShaderBlendSettings::Factor::OneMinusConstantAlpha; LibGL::ONE_MINUS_CONSTANT_ALPHA 232 | else 233 | raise ArgumentError.new "Invalid blend factor given!" 234 | end 235 | end 236 | 237 | def self.translate_blend_func(func : ShaderBlendSettings::Function) 238 | case func 239 | when ShaderBlendSettings::Function::Add; LibGL::FUNC_ADD 240 | when ShaderBlendSettings::Function::Subtract; LibGL::FUNC_SUBTRACT 241 | when ShaderBlendSettings::Function::ReverseSubtract; LibGL::FUNC_REVERSE_SUBTRACT 242 | when ShaderBlendSettings::Function::Min; LibGL::MIN 243 | when ShaderBlendSettings::Function::Max; LibGL::MAX 244 | else 245 | raise ArgumentError.new "Invalid blend function given!" 246 | end 247 | end 248 | end 249 | 250 | class Boleite::Private::OpenGLShaderObject 251 | @object_id : LibGL::UInt 252 | @type : ShaderType 253 | 254 | def initialize(source : String, type : ShaderType) 255 | @object_id = GL.safe_call { LibGL.createShader self.class.translate_shader_type(type) } 256 | @type = type 257 | 258 | GL.safe_call do 259 | conv_source = [source.to_unsafe.as(LibGL::Char*)].to_unsafe.as(LibGL::Char*) 260 | LibGL.shaderSource @object_id, 1, conv_source, nil 261 | LibGL.compileShader @object_id 262 | 263 | LibGL.getShaderiv @object_id, LibGL::COMPILE_STATUS, out status 264 | if status == LibGL::FALSE 265 | LibGL.getShaderiv @object_id, LibGL::INFO_LOG_LENGTH, out length 266 | info_log = Slice(LibGL::Char).new(length + 1) 267 | LibGL.getShaderInfoLog @object_id, length, nil, info_log 268 | message = String.new(info_log.to_unsafe.as(UInt8*).to_slice(length + 1)) 269 | raise ShaderException.new @type, message 270 | end 271 | end 272 | end 273 | 274 | def finalize 275 | if @object_id > 0 276 | GL.safe_call { LibGL.deleteShader @object_id } 277 | @object_id = LibGL::UInt.zero 278 | end 279 | end 280 | 281 | def gl_identifier 282 | @object_id 283 | end 284 | 285 | def self.translate_shader_type(type : ShaderType) 286 | case type 287 | when ShaderType::Vertex; LibGL::VERTEX_SHADER 288 | when ShaderType::Geometry; LibGL::GEOMETRY_SHADER 289 | when ShaderType::Fragment; LibGL::FRAGMENT_SHADER 290 | else 291 | raise ArgumentError.new "Invalid shader type given!(#{type})" 292 | end 293 | end 294 | end -------------------------------------------------------------------------------- /src/boleite/backend/graphics/opengl_texture.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Private::OpenGLTexture < Boleite::Texture 2 | include CrystalClear 3 | 4 | @@internal_formats = { 5 | {Format::Red, Type::Integer8} => LibGL::R8, {Format::Red, Type::Integer16} => LibGL::R16, 6 | {Format::Red, Type::Float16} => LibGL::R16F, {Format::Red, Type::Float32} => LibGL::R32F, 7 | {Format::RG, Type::Integer8} => LibGL::RG8, {Format::RG, Type::Integer16} => LibGL::RG16, 8 | {Format::RG, Type::Float16} => LibGL::RG16F, {Format::RG, Type::Float32} => LibGL::RG32F, 9 | {Format::RGB, Type::Integer8} => LibGL::RGB8, {Format::RGB, Type::Integer16} => LibGL::RGB16, 10 | {Format::RGB, Type::Float16} => LibGL::RGB16F, {Format::RGB, Type::Float32} => LibGL::RGB32F, 11 | {Format::RGBA, Type::Integer8} => LibGL::RGBA8, {Format::RGBA, Type::Integer16} => LibGL::RGBA16, 12 | {Format::RGBA, Type::Float16} => LibGL::RGBA16F, {Format::RGBA, Type::Float32} => LibGL::RGBA32F, 13 | } 14 | 15 | @size = Vector2u.zero 16 | @depth = false 17 | @smooth = true 18 | @repeating = false 19 | @object_id : LibGL::UInt = 0u32 20 | 21 | def self.translate_format(format, type) 22 | @@internal_formats[{format, type}] 23 | end 24 | 25 | def self.translate_external_format(format) 26 | case format 27 | when Format::Red; LibGL::RED 28 | when Format::RG; LibGL::RG 29 | when Format::RGB; LibGL::RGB 30 | when Format::RGBA; LibGL::RGBA 31 | else 32 | raise ArgumentError.new "Invalid external data format given!(#{format})" 33 | end 34 | end 35 | 36 | def self.translate_unpack_alignment(format) 37 | case format 38 | when Format::RGBA; 4 39 | when Format::RGB; 3 40 | when Format::RG; 2 41 | else; 1 42 | end 43 | end 44 | 45 | def self.maximum_size : UInt32 46 | size = 0 47 | GL.safe_call { LibGL.getIntegerv LibGL::MAX_TEXTURE_SIZE, pointerof(size) } 48 | size.to_u 49 | end 50 | 51 | def initialize 52 | GL.safe_call { LibGL.genTextures 1, pointerof(@object_id) } 53 | @format = Format::Red 54 | @type = Type::Integer8 55 | end 56 | 57 | def finalize 58 | GL.safe_call { LibGL.deleteTextures 1, pointerof(@object_id) } 59 | end 60 | 61 | def create(width : UInt32, height : UInt32, @format : Format, @type : Type) : Void 62 | @size = Vector2u.new(width, height) 63 | @depth = false 64 | 65 | internal_format = self.class.translate_format(format, type) 66 | create_internal width, height, internal_format 67 | end 68 | 69 | def create_depth(width : UInt32, height : UInt32) : Void 70 | @size = Vector2u.new(width, height) 71 | @format = Format::Red 72 | @type = Format::Float32 73 | @depth = true 74 | create_internal width, height LibGL::DEPTH_COMPONENT 75 | end 76 | 77 | def create_internal(width, height, format) 78 | activate do 79 | GL.safe_call do 80 | LibGL.texImage2D LibGL::TEXTURE_2D, 0, format, width, height, 0, LibGL::RGBA, LibGL::UNSIGNED_BYTE, nil 81 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_MIN_FILTER, @smooth ? LibGL::LINEAR : LibGL::NEAREST 82 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_MAG_FILTER, @smooth ? LibGL::LINEAR : LibGL::NEAREST 83 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_WRAP_S, @repeating ? LibGL::REPEAT : LibGL::CLAMP 84 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_WRAP_T, @repeating ? LibGL::REPEAT : LibGL::CLAMP 85 | end 86 | end 87 | end 88 | 89 | requires x_dest + width <= @size.x 90 | requires y_dest + height <= @size.y 91 | requires @depth == false 92 | def update(pixels : Pointer(UInt8), width, height, x_dest, y_dest, format : Format) : Void 93 | activate do 94 | GL.safe_call do 95 | external_format = self.class.translate_external_format format 96 | alignment = self.class.translate_unpack_alignment @format 97 | LibGL.pixelStorei LibGL::UNPACK_ALIGNMENT, alignment 98 | LibGL.texSubImage2D LibGL::TEXTURE_2D, 0, x_dest, y_dest, width, height, external_format, LibGL::UNSIGNED_BYTE, pixels 99 | LibGL.pixelStorei LibGL::UNPACK_ALIGNMENT, 4 100 | end 101 | end 102 | end 103 | 104 | requires x_dest + width <= @size.x 105 | requires y_dest + height <= @size.y 106 | requires @depth == false 107 | def update(pixels : Pointer(Float32), width, height, x_dest, y_dest, format : Format) : Void 108 | activate do 109 | GL.safe_call do 110 | external_format = self.class.translate_external_format format 111 | alignment = self.class.translate_unpack_alignment @format 112 | LibGL.pixelStorei LibGL::UNPACK_ALIGNMENT, alignment 113 | LibGL.texSubImage2D LibGL::TEXTURE_2D, 0, x_dest, y_dest, width, height, external_format, LibGL::FLOAT, pixels 114 | LibGL.pixelStorei LibGL::UNPACK_ALIGNMENT, 4 115 | end 116 | end 117 | end 118 | 119 | 120 | requires x + texture.size.x <= @size.x 121 | requires y + texture.size.y <= @size.y 122 | def update(texture, x, y) : Void 123 | src_fb = OpenGLFrameBuffer.new 124 | dst_fb = OpenGLFrameBuffer.new 125 | tex_size = texture.size.to_i 126 | 127 | src_fb.attach_buffer texture, :src, 0u8 128 | dst_fb.attach_buffer self, :src, 0u8 129 | dst_fb.blit src_fb, IntRect.new(0, 0, tex_size.x, tex_size.y), IntRect.new(x, y, x + tex_size.x, y + tex_size.y) 130 | end 131 | 132 | def size : Vector2u 133 | @size 134 | end 135 | 136 | def format : Format 137 | @format 138 | end 139 | 140 | def type : Type 141 | @type 142 | end 143 | 144 | def is_depth? : Bool 145 | @depth 146 | end 147 | 148 | def is_smooth? : Bool 149 | @smooth 150 | end 151 | 152 | def smooth=(val : Bool) : Bool 153 | @smooth = val 154 | activate do 155 | GL.safe_call do 156 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_MIN_FILTER, @smooth ? LibGL::LINEAR : LibGL::NEAREST 157 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_MAG_FILTER, @smooth ? LibGL::LINEAR : LibGL::NEAREST 158 | end 159 | end 160 | @smooth 161 | end 162 | 163 | def is_repeating? : Bool 164 | @repeating 165 | end 166 | 167 | def repeating=(val : Bool) : Bool 168 | @repeating = val 169 | activate do 170 | GL.safe_call do 171 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_WRAP_S, @repeating ? LibGL::REPEAT : LibGL::CLAMP 172 | LibGL.texParameteri LibGL::TEXTURE_2D, LibGL::TEXTURE_WRAP_T, @repeating ? LibGL::REPEAT : LibGL::CLAMP 173 | end 174 | end 175 | @repeating 176 | end 177 | 178 | def activate(&block) 179 | GL.safe_call { LibGL.bindTexture LibGL::TEXTURE_2D, @object_id } 180 | result = yield 181 | GL.safe_call { LibGL.bindTexture LibGL::TEXTURE_2D, 0 } 182 | result 183 | end 184 | 185 | def bind 186 | GL.safe_call { LibGL.bindTexture LibGL::TEXTURE_2D, @object_id } 187 | end 188 | 189 | def identifier 190 | @object_id 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /src/boleite/backend/graphics/opengl_vertex_buffer.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Private::OpenGLVertexBufferObject < Boleite::VertexBufferObject 2 | @object_id : LibGL::UInt = 0_u32 3 | 4 | def initialize 5 | @activated = false 6 | GL.safe_call { LibGL.genVertexArrays 1, pointerof(@object_id) } 7 | super 8 | end 9 | 10 | def finalize 11 | GL.safe_call { LibGL.deleteVertexArrays 1, pointerof(@object_id) } 12 | end 13 | 14 | def allocate_buffer : VertexBuffer 15 | OpenGLVertexBuffer.new 16 | end 17 | 18 | def render(instances) 19 | activate do 20 | @buffers.each do |buffer| 21 | buffer.build 22 | end 23 | update_layout 24 | 25 | primitive = self.class.translate_primitive(@primitive) 26 | if @indices_buffer 27 | GL.safe_call { LibGL.drawElementsInstanced primitive, num_vertices, LibGL::UNSIGNED_INT, nil, instances } 28 | else 29 | GL.safe_call { LibGL.drawArraysInstanced primitive, 0, num_vertices, instances } 30 | end 31 | 32 | clear_tmp_buffers 33 | end 34 | end 35 | 36 | def update_layout 37 | if @update_layout 38 | activate do 39 | @layout.attributes.each_index do |index| 40 | attribute = @layout.attributes[index] 41 | attribute_type = self.class.translate_type(attribute.type) 42 | attribute_offset = Pointer(Void).new(attribute.offset) 43 | buffer = @buffers[attribute.buffer] 44 | buffer.activate 45 | GL.safe_call { LibGL.enableVertexAttribArray index } 46 | GL.safe_call { LibGL.vertexAttribPointer index, attribute.size, attribute_type, LibGL::FALSE, attribute.stride, attribute_offset } 47 | GL.safe_call { LibGL.vertexAttribDivisor index, attribute.frequency } 48 | end 49 | end 50 | @update_layout = false 51 | end 52 | end 53 | 54 | def activate(&block) 55 | was_activated = @activated 56 | GL.safe_call { LibGL.bindVertexArray @object_id } unless was_activated 57 | @activated = true 58 | result = yield 59 | @activated = false unless was_activated 60 | GL.safe_call { LibGL.bindVertexArray 0 } unless was_activated 61 | result 62 | end 63 | 64 | def self.translate_primitive(primitive : Primitive) 65 | case primitive 66 | when Primitive::Points; LibGL::POINTS 67 | when Primitive::Lines; LibGL::LINES 68 | when Primitive::LinesStrip; LibGL::LINE_STRIP 69 | when Primitive::Triangles; LibGL::TRIANGLES 70 | when Primitive::TrianglesStrip; LibGL::TRIANGLE_STRIP 71 | when Primitive::TriangleFan; LibGL::TRIANGLE_FAN 72 | else raise ArgumentError.new "Invalid primitive given! Received #{primitive}" 73 | end 74 | end 75 | 76 | def self.translate_type(type : VertexAttribute::Type) 77 | case type 78 | when VertexAttribute::Type::Float; LibGL::FLOAT 79 | when VertexAttribute::Type::Double; LibGL::DOUBLE 80 | when VertexAttribute::Type::Int; LibGL::INT 81 | else raise ArgumentError.new "Invalid type given! Received #{type}" 82 | end 83 | end 84 | end 85 | 86 | class Boleite::Private::OpenGLVertexBuffer < Boleite::VertexBuffer 87 | @buffer_id : LibGL::UInt = 0_u32 88 | @target = LibGL::ARRAY_BUFFER 89 | 90 | def initialize 91 | GL.safe_call { LibGL.genBuffers 1, pointerof(@buffer_id) } 92 | end 93 | 94 | def finalize 95 | GL.safe_call { LibGL.deleteBuffers 1, pointerof(@buffer_id) } 96 | end 97 | 98 | def activate 99 | GL.safe_call { LibGL.bindBuffer @target, @buffer_id } 100 | end 101 | 102 | def set_vertices_target 103 | @target = LibGL::ARRAY_BUFFER 104 | end 105 | 106 | def set_indices_target 107 | @target = LibGL::ELEMENT_ARRAY_BUFFER 108 | end 109 | 110 | def build 111 | if needs_rebuild? 112 | activate 113 | GL.safe_call { LibGL.bufferData @target, size, @data.to_unsafe, LibGL::STATIC_DRAW } 114 | @rebuild = false 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /src/boleite/backend/graphics_context.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::GraphicsContext 2 | abstract def main_target : RenderTarget 3 | abstract def clear(color : Colorf) : Void 4 | abstract def clear_depth() : Void 5 | abstract def present : Void 6 | 7 | abstract def scissor=(rect : IntRect) : Void 8 | abstract def scissor=(arg : Nil) : Void 9 | 10 | abstract def create_vertex_buffer_object : VertexBufferObject 11 | abstract def create_vertex_buffer : VertexBuffer 12 | abstract def create_shader(parser : ShaderParser) : Shader 13 | abstract def create_texture() : Texture 14 | abstract def create_frame_buffer() : FrameBuffer 15 | 16 | abstract def texture_maximum_size : UInt32 17 | end 18 | -------------------------------------------------------------------------------- /src/boleite/backend/opengl_helper.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Private::GL 2 | def self.safe_call 3 | check_errors 4 | result = yield 5 | check_errors 6 | result 7 | end 8 | 9 | private def self.check_errors 10 | error = LibGL.getError 11 | if error != LibGL::NO_ERROR 12 | id = id_error error 13 | desc = desc_error error 14 | raise BackendException.new "OpenGL error: #{desc} (#{id})" 15 | end 16 | end 17 | 18 | private def self.id_error(error) 19 | case error 20 | when LibGL::INVALID_ENUM; "GL_INVALID_ENUM" 21 | when LibGL::INVALID_VALUE; "GL_INVALID_VALUE" 22 | when LibGL::INVALID_OPERATION; "GL_INVALID_OPERATION" 23 | when LibGL::STACK_OVERFLOW; "GL_STACK_OVERFLOW" 24 | when LibGL::STACK_UNDERFLOW; "GL_STACK_UNDERFLOW" 25 | when LibGL::OUT_OF_MEMORY; "GL_OUT_OF_MEMORY" 26 | when LibGL::INVALID_FRAMEBUFFER_OPERATION; "GL_INVALID_FRAMEBUFFER_OPERATION" 27 | when LibGL::CONTEXT_LOST; "GL_CONTEXT_LOST" 28 | else "" 29 | end 30 | end 31 | 32 | private def self.desc_error(error) 33 | case error 34 | when LibGL::INVALID_ENUM; "An invalid enumeration parameter was given to an OpenGL function." 35 | when LibGL::INVALID_VALUE; "An invalid value parameter was given to an OpenGL function." 36 | when LibGL::INVALID_OPERATION; "An OpenGL function called with invalid state or combination of parameters." 37 | when LibGL::STACK_OVERFLOW; "Stack push would overflow stack size." 38 | when LibGL::STACK_UNDERFLOW; "Stack pop would underflow stack size." 39 | when LibGL::OUT_OF_MEMORY; "Out of Graphics Memory." 40 | when LibGL::INVALID_FRAMEBUFFER_OPERATION; "Read/Write operation on an incomplete framebuffer." 41 | when LibGL::CONTEXT_LOST; "Graphics Context has been lost." 42 | else "" 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /src/boleite/backend/truetype.cr: -------------------------------------------------------------------------------- 1 | @[Link("freetype")] 2 | lib LibFreeType 3 | enum Encoding : UInt32 4 | NONE = 0 5 | 6 | MS_SYMBOL = 1937337698 7 | UNICODE = 1970170211 8 | end 9 | 10 | enum Load 11 | DEFAULT = 0 12 | NO_SCALE = 1 << 0 13 | NO_HINTING = 1 << 1 14 | RENDER = 1 << 2 15 | NO_BITMAP = 1 << 3 16 | VERTICAL_LAYOUT = 1 << 4 17 | FORCE_AUTOHINT = 1 << 5 18 | CROP_BITMAP = 1 << 6 19 | PEDANTIC = 1 << 7 20 | IGNORE_GLOBAL_ADVANCE_WIDTH = 1 << 9 21 | NO_RECURSE = 1 << 10 22 | IGNORE_TRANSFORM = 1 << 11 23 | MONOCHROME = 1 << 12 24 | LINEAR_DESIGN = 1 << 13 25 | NO_AUTOHINT = 1 << 15 26 | end 27 | 28 | enum FaceFlag 29 | SCALABLE = 1 << 0 30 | FIXED_SIZES = 1 << 1 31 | FIXED_WIDTH = 1 << 2 32 | SFNT = 1 << 3 33 | HORIZONTAL = 1 << 4 34 | VERTICAL = 1 << 5 35 | KERNING = 1 << 6 36 | FAST_GLYPHS = 1 << 7 37 | MULTIPLE_MASTERS = 1 << 8 38 | GLYPH_NAMES = 1 << 9 39 | EXTERNAL_STREAM = 1 << 10 40 | HINTER = 1 << 11 41 | CID_KEYED = 1 << 12 42 | TRICKY = 1 << 13 43 | COLOR = 1 << 14 44 | end 45 | 46 | enum Render_Mode 47 | NORMAL = 0 48 | LIGHT 49 | MONO 50 | LCD 51 | LCD_V 52 | end 53 | 54 | alias Error = Int32 55 | alias Long = LibC::Long 56 | alias ULong = LibC::ULong 57 | alias Int = LibC::Int 58 | alias UInt = LibC::UInt 59 | alias F26Dot6 = LibC::Long 60 | alias Pos = LibC::Long 61 | alias Fixed = LibC::Long 62 | 63 | type Library = Void* 64 | 65 | struct Bitmap 66 | rows : LibC::UInt 67 | width : LibC::UInt 68 | pitch : LibC::Int 69 | buffer : LibC::UChar* 70 | num_grays : LibC::UShort 71 | pixel_mode : LibC::UChar 72 | palette_mode : LibC::UChar 73 | palette : Void* 74 | end 75 | 76 | struct Vector 77 | x : Pos 78 | y : Pos 79 | end 80 | 81 | struct Generic 82 | data : Void* 83 | finalizer : Void* 84 | end 85 | 86 | struct SizeMetrics 87 | x_ppem : LibC::UShort 88 | y_ppem : LibC::UShort 89 | 90 | x_scale : Fixed 91 | y_scale : Fixed 92 | 93 | ascender : Pos 94 | descender : Pos 95 | height : Pos 96 | max_advance : Pos 97 | end 98 | 99 | struct GlyphSlotRec 100 | padding1 : UInt8[128] # How far to the first member I want 101 | advance : Vector 102 | padding2 : UInt8[8] # How far to the second member I want 103 | bitmap : Bitmap 104 | bitmap_left : Int 105 | bitmap_top : Int 106 | end 107 | type GlyphSlot = GlyphSlotRec* 108 | 109 | struct SizeRec 110 | face : Face 111 | generic : Generic 112 | metrics : SizeMetrics 113 | end 114 | type Size = SizeRec* 115 | 116 | struct FaceRec 117 | padding1 : UInt8[16] # How far to the first member I want 118 | face_flags : Long 119 | padding : UInt8[128] # How far to the second member I want 120 | glyph : GlyphSlot 121 | size : Size 122 | end 123 | type Face = FaceRec* 124 | 125 | Err_Ok = Error.new(0) 126 | 127 | fun init_FreeType = FT_Init_FreeType(library : Library*) : Error 128 | fun done_FreeType = FT_Done_FreeType(library : Library) : Error 129 | 130 | fun new_Face = FT_New_Face(library : Library, path : LibC::Char*, face_index : Long, face : Face*) : Error 131 | fun done_Face = FT_Done_Face(face : Face) : Error 132 | fun select_Charmap = FT_Select_Charmap(face : Face, encoding : Encoding) : Error 133 | 134 | fun set_Pixel_Sizes = FT_Set_Pixel_Sizes(face : Face, width : UInt, height : UInt ) : Error 135 | fun set_Char_Size = FT_Set_Char_Size(face : Face, width : F26Dot6, height : F26Dot6, horz_resolution : UInt, vert_resolution : UInt) : Error 136 | fun get_Char_Index = FT_Get_Char_Index(face : Face, code : ULong) : UInt 137 | fun load_Char = FT_Load_Char(face : Face, code : ULong, flags : Int32) : Error 138 | 139 | fun load_Glyph = FT_Load_Glyph(face : Face, index : UInt, flags : Int32) : Error 140 | fun render_Glyph = FT_Render_Glyph(glyph : GlyphSlot, render : Render_Mode ) : Error 141 | 142 | fun get_Kerning = FT_Get_Kerning(face : Face, left : UInt, right : UInt, kern_mode : UInt, kerning : Vector*) : Error 143 | end -------------------------------------------------------------------------------- /src/boleite/backend/video_mode.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::VideoMode 2 | property :resolution 3 | property :mode 4 | property :refresh_rate 5 | 6 | enum Mode : UInt8 7 | Windowed 8 | Fullscreen 9 | Borderless 10 | end 11 | 12 | @resolution = Vector2u.zero 13 | @mode = Mode::Windowed 14 | @refresh_rate = UInt16.zero 15 | 16 | def initialize() 17 | end 18 | 19 | def initialize(width, height, @mode, @refresh_rate = UInt16.zero) 20 | @resolution.x = width 21 | @resolution.y = height 22 | end 23 | 24 | def any_refresh_rate? 25 | @refresh_rate.zero? 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /src/boleite/clock.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Clock 2 | def initialize 3 | @start = Time.utc 4 | end 5 | 6 | def restart 7 | old = @start 8 | @start = Time.utc 9 | @start - old 10 | end 11 | 12 | def elapsed 13 | Time.utc_now - @start 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/boleite/graphics/camera.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Camera 2 | @update_transform = true 3 | 4 | def initialize(@projection : Matrix44f32) 5 | @transform = Matrix44f32.identity 6 | end 7 | 8 | def transformation 9 | if @update_transform 10 | update_transformation 11 | @update_transform = false 12 | end 13 | @transform 14 | end 15 | 16 | def inverse_transformation 17 | Matrix.inverse self.transformation 18 | end 19 | 20 | def projection 21 | @projection 22 | end 23 | 24 | def inverse_projection 25 | Matrix.inverse self.projection 26 | end 27 | 28 | abstract def update_transformation 29 | end -------------------------------------------------------------------------------- /src/boleite/graphics/camera_2d.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Camera2D < Boleite::Camera 2 | @pos = Vector2f.zero 3 | @scale = 1.0 4 | 5 | property width, height, near, far 6 | getter scale 7 | 8 | def initialize(width : Float32, height : Float32, near : Float32, far : Float32) 9 | super(Matrix.calculate_ortho_projection(0.0f32, width, 0.0f32, height, near, far)) 10 | @width = width 11 | @height = height 12 | @near = near 13 | @far = far 14 | end 15 | 16 | def position 17 | @pos 18 | end 19 | 20 | def position=(pos) 21 | @update_transform = @update_transform || @pos != pos 22 | @pos = pos 23 | end 24 | 25 | def scale=(scale) 26 | @update_transform = @update_transform || @scale != scale 27 | @scale = scale 28 | end 29 | 30 | def move(offset) 31 | self.position = position + offset 32 | end 33 | 34 | def move(x, y) 35 | self.position = position + Vector2f.new(x, y) 36 | end 37 | 38 | def update_transformation 39 | p = @pos.to_f32 40 | s = @scale.to_f32 41 | @transform = Matrix44f32.identity 42 | @transform = Matrix.translate @transform, Vector3f32.new(p.x, p.y, 0f32) 43 | @transform = Matrix.scale @transform, Vector4f32.new(s, s, s, 1f32) 44 | end 45 | end -------------------------------------------------------------------------------- /src/boleite/graphics/camera_3d.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Camera3D < Boleite::Camera 2 | @pos = Vector3f.zero 3 | @rot = Vector3f.zero 4 | 5 | getter width, height, near, far 6 | 7 | def initialize(@fov : Float32, @width : Float32, @height : Float32, @near : Float32, @far : Float32) 8 | aspect = width / height 9 | fov = @fov * (Math::PI / 180) 10 | super(Matrix.calculate_fov_projection(fov, aspect, @near, @far, true)) 11 | end 12 | 13 | def position 14 | @pos 15 | end 16 | 17 | def position=(pos) 18 | @pos = pos 19 | @update_transform = true 20 | end 21 | 22 | def rotation 23 | @rot 24 | end 25 | 26 | def rotation=(rot) 27 | @rot = rot 28 | @update_transform = true 29 | end 30 | 31 | def move(offset) 32 | self.position = position + offset 33 | end 34 | 35 | def move(x, y, z) 36 | self.position = position + Vector3f.new(x, y, z) 37 | end 38 | 39 | def rotate(vec) 40 | self.rotation = rotation + vec 41 | end 42 | 43 | def rotate(x, y, z) 44 | self.rotation = rotation + Vector3f.new(x, y, z) 45 | end 46 | 47 | def update_transformation 48 | @transform = Matrix44f32.identity 49 | @transform = Matrix.translate @transform, @pos.to_f32 50 | x = Matrix.rotate_around_x Matrix44f32.identity, @rot.x.to_f32 51 | y = Matrix.rotate_around_y Matrix44f32.identity, @rot.y.to_f32 52 | z = Matrix.rotate_around_z Matrix44f32.identity, @rot.z.to_f32 53 | rot = Matrix.mul x, Matrix.mul y, z 54 | @transform = Matrix.mul rot, @transform 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /src/boleite/graphics/circle_shape.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Drawable 2 | end 3 | 4 | module Boleite::Transformable 5 | end 6 | 7 | class Boleite::CircleShape 8 | include Drawable 9 | include Transformable 10 | 11 | @rebuild = true 12 | @shape = Shape.new Primitive::TriangleFan 13 | 14 | getter radius 15 | getter quality = 32 16 | 17 | delegate :color, :color=, to: @shape 18 | 19 | def initialize(@radius = 1.0) 20 | end 21 | 22 | def radius=(@radius) 23 | @rebuild = true 24 | end 25 | 26 | def quality=(@quality) 27 | @rebuild = true 28 | end 29 | 30 | private def internal_render(renderer, transform) 31 | build_circle if @rebuild 32 | transform = Matrix.mul transform, self.transformation 33 | renderer.draw @shape, transform 34 | end 35 | 36 | def build_circle 37 | @shape.clear_vertices 38 | @shape.add_vertex Boleite::Vector2f32.zero 39 | step = -(Math::PI * 2.0) / @quality 40 | (0..@quality).each do |index| 41 | point1 = Boleite::Vector2f.new @radius * Math.cos(index * step), @radius * Math.sin(index * step) 42 | point2 = Boleite::Vector2f.new @radius * Math.cos((index + 1) * step), @radius * Math.sin((index + 1) * step) 43 | 44 | @shape.add_vertex point1.to_f32 45 | @shape.add_vertex point2.to_f32 46 | end 47 | @rebuild = false 48 | end 49 | end -------------------------------------------------------------------------------- /src/boleite/graphics/drawable.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Drawable 2 | def render(renderer : Renderer, transform : Matrix44f32) : Void 3 | internal_render(renderer, transform) 4 | end 5 | end -------------------------------------------------------------------------------- /src/boleite/graphics/font.cr: -------------------------------------------------------------------------------- 1 | require "./font/glyph.cr" 2 | require "./font/page.cr" 3 | require "./font/row.cr" 4 | 5 | class Boleite::Font 6 | class Error < Exception 7 | end 8 | 9 | @gfx : GraphicsContext 10 | @pages = {} of UInt32 => Page 11 | 12 | def initialize(@gfx, file) 13 | error = LibFreeType.init_FreeType(out @library) 14 | raise Error.new("Failed initialization of FreeType") if error != LibFreeType::Err_Ok 15 | error = LibFreeType.new_Face @library, file, 0, out @face 16 | raise Error.new("Failed to read #{file}") if error != LibFreeType::Err_Ok 17 | error = LibFreeType.select_Charmap(@face, LibFreeType::Encoding::UNICODE) 18 | raise Error.new("Failed to select unicode charset for #{file}") if error != LibFreeType::Err_Ok 19 | end 20 | 21 | def finalize 22 | LibFreeType.done_Face @face 23 | LibFreeType.done_FreeType @library 24 | end 25 | 26 | def texture_for(size) : Texture 27 | page = get_page size 28 | page.texture 29 | end 30 | 31 | def get_glyph(code : Char, size : UInt32) 32 | page = get_page size 33 | key = Glyph.generate_hash code 34 | glyph = page.glyphs[key]? 35 | if glyph.nil? 36 | glyph = page.create_glyph code 37 | end 38 | return glyph 39 | end 40 | 41 | def get_kerning(first : Char, second : Char, size : UInt32) 42 | page = get_page size 43 | page.get_kerning first, second 44 | end 45 | 46 | def get_linespacing(size : UInt32) : Float64 47 | page = get_page size 48 | page.get_linespacing 49 | end 50 | 51 | def get_page(size) : Page 52 | page = @pages[size]? 53 | unless page 54 | page = Page.new @face, @gfx, size 55 | @pages[size] = page 56 | end 57 | page 58 | end 59 | end -------------------------------------------------------------------------------- /src/boleite/graphics/font/glyph.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Font 2 | class Glyph 3 | property advance, bounds, texture_rect, code 4 | 5 | @advance = 0i64 6 | @bounds = FloatRect.new 7 | @texture_rect = IntRect.new 8 | @code = Char::ZERO 9 | 10 | def self.generate_hash(code) : UInt64 11 | code.hash.to_u64 12 | end 13 | 14 | def generate_hash : UInt64 15 | self.class.generate_hash @code 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /src/boleite/graphics/font/page.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Font 2 | class Page 3 | property glyphs, texture, next_row, rows 4 | 5 | @gfx : GraphicsContext 6 | @face : LibFreeType::Face 7 | @texture : Texture 8 | @size : UInt32 9 | @glyphs = {} of UInt64 => Glyph 10 | @next_row = 0u32 11 | @rows = [] of Row 12 | 13 | def initialize(@face, @gfx, @size) 14 | @texture = @gfx.create_texture 15 | @texture.create 64u32, 64u32, Texture::Format::Red, Texture::Type::Integer8 16 | end 17 | 18 | def character_size 19 | @size 20 | end 21 | 22 | def create_glyph(code) : Glyph 23 | load_glyph code 24 | glyph = render_glyph 25 | glyph.code = code 26 | @glyphs[glyph.generate_hash] = glyph 27 | end 28 | 29 | def get_kerning(first, second) : Int32 30 | if @face.value.face_flags & LibFreeType::FaceFlag::KERNING.to_i 31 | apply_size 32 | index1 = LibFreeType.get_Char_Index @face, first.hash 33 | index2 = LibFreeType.get_Char_Index @face, second.hash 34 | error = LibFreeType.get_Kerning @face, index1, index2, 0, out kerning 35 | raise Error.new("Failed to fetch kerning between #{first} and #{second}") if error != LibFreeType::Err_Ok 36 | return kerning.x.to_i unless @face.value.face_flags & LibFreeType::FaceFlag::SCALABLE.to_i 37 | return (kerning.x >> 6).to_i 38 | else 39 | 0 40 | end 41 | end 42 | 43 | def get_linespacing : Float64 44 | apply_size 45 | metrics = @face.value.size.value.metrics 46 | metrics.height.to_f / (1 << 6).to_f 47 | end 48 | 49 | private def apply_size 50 | error = LibFreeType.set_Pixel_Sizes @face, @size, @size 51 | raise Error.new("Failed to set charset size to #{@size}") if error != LibFreeType::Err_Ok 52 | end 53 | 54 | private def load_glyph(code) 55 | apply_size 56 | flags = LibFreeType::Load::RENDER 57 | error = LibFreeType.load_Char @face, code.ord, flags 58 | raise Error.new("Failed to load glyph for #{code}") if error != LibFreeType::Err_Ok 59 | end 60 | 61 | private def render_glyph : Glyph 62 | glyph = Glyph.new 63 | raw_glyph = @face.value.glyph 64 | bitmap = raw_glyph.value.bitmap 65 | glyph.advance = raw_glyph.value.advance.x >> 6 66 | width, height = bitmap.width + 2, bitmap.rows + 2 # Add padding to glyph space 67 | texture_rect = find_glyph_rect(width, height) 68 | texture_rect.left += 1 69 | texture_rect.top += 1 70 | texture_rect.width -= 2 71 | texture_rect.height -= 2 72 | glyph.texture_rect = texture_rect 73 | bounds = glyph.bounds 74 | bounds.left = raw_glyph.value.bitmap_left.to_f 75 | bounds.top = raw_glyph.value.bitmap.rows - raw_glyph.value.bitmap_top.to_f 76 | bounds.width = raw_glyph.value.bitmap.width.to_f 77 | bounds.height = raw_glyph.value.bitmap.rows.to_f 78 | glyph.bounds = bounds 79 | render_glyph_to_texture glyph, width, height 80 | glyph 81 | end 82 | 83 | private def find_glyph_rect(width, height) 84 | row = find_row_for width, height 85 | rect = IntRect.new row.width.to_i, row.top.to_i, width.to_i, height.to_i 86 | row.width += width 87 | rect 88 | end 89 | 90 | private def find_row_for(width, height) : Row 91 | best_ratio = 0.0 92 | best_row = nil 93 | @rows.each do |row| 94 | ratio = height.to_f / row.height.to_f 95 | next if ratio < 0.7 || ratio > 1.0 96 | next if width > @texture.size.x - row.width 97 | next if ratio < best_ratio 98 | best_row = row 99 | best_ratio = ratio 100 | end 101 | best_row = create_row(width, height) if best_row.nil? 102 | best_row 103 | end 104 | 105 | private def create_row(width, height) : Row 106 | row_height = height + height // 10 107 | size = @texture.size 108 | max_size = @gfx.texture_maximum_size 109 | while @next_row + row_height >= size.y || width >= size.x 110 | size = @texture.size * 2u32 111 | if size.x > max_size && size.y > max_size 112 | raise Error.new "Font too large, Maximum texture size reached" 113 | end 114 | 115 | texture = @gfx.create_texture 116 | texture.create size.x, size.y, Texture::Format::Red, Texture::Type::Integer8 117 | texture.update @texture 118 | @texture = texture 119 | end 120 | row = Row.new @next_row, row_height 121 | @rows << row 122 | @next_row += row_height 123 | row 124 | end 125 | 126 | private def render_glyph_to_texture(glyph, width, height) : Nil 127 | bitmap = @face.value.glyph.value.bitmap 128 | buffer = Bytes.new bitmap.buffer, (bitmap.width * bitmap.rows).to_i 129 | pixels = Bytes.new (width * height).to_i, 0u8 130 | (1...height-1).each do |y| 131 | (1...width-1).each do |x| 132 | index = x + y * width 133 | pixels[index] = buffer[x - 1] 134 | end 135 | buffer += bitmap.pitch 136 | end 137 | rect = glyph.texture_rect 138 | 139 | x = rect.left - 1 140 | y = rect.top - 1 141 | w = rect.width + 2 142 | h = rect.height + 2 143 | @texture.update(pixels, w.to_u32, h.to_u32, x.to_u32, y.to_u32, Texture::Format::Red) 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /src/boleite/graphics/font/row.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Font 2 | class Row 3 | property width, top, height 4 | 5 | @width = 0u32 6 | @top = 0u32 7 | @height = 0u32 8 | 9 | def initialize(@top, @height) 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /src/boleite/graphics/forward_renderer.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Renderer 2 | end 3 | 4 | class Boleite::ForwardRenderer < Boleite::Renderer 5 | def initialize(@gfx : GraphicsContext, @camera : Camera, @default_shader : Shader) 6 | end 7 | 8 | def clear(color : Colorf) : Void 9 | @gfx.clear color 10 | @gfx.clear_depth 11 | end 12 | 13 | def draw(drawcall : DrawCallContext) : Void 14 | shader = ensure_shader drawcall.shader 15 | apply_shader_settings shader, drawcall.transformation, drawcall.uniforms 16 | drawcall.buffers.each { |buffer| drawcall.vertices.attach_buffer(buffer, true) } 17 | shader.activate do 18 | drawcall.vertices.render(1) 19 | end 20 | end 21 | 22 | def present : Void 23 | @gfx.present 24 | end 25 | 26 | def ensure_shader(shader) : Shader 27 | if shader 28 | shader 29 | else 30 | @default_shader 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /src/boleite/graphics/frame_buffer.cr: -------------------------------------------------------------------------------- 1 | module Boleite 2 | abstract class FrameBuffer 3 | abstract def activate(&block) 4 | abstract def attach_buffer(texture : Texture, identifier : Symbol, slot : UInt8): Void 5 | abstract def attach_depth_buffer(texture : Texture) : Void 6 | abstract def detach_buffer(identifier : Symbol) : Void 7 | abstract def detach_depth_buffer() : Void 8 | abstract def detach_all_buffers() : Void 9 | end 10 | end -------------------------------------------------------------------------------- /src/boleite/graphics/image.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Image 2 | include CrystalClear 3 | class Error < Exception 4 | end 5 | 6 | @width : UInt32 7 | @height : UInt32 8 | @bpp : UInt32 9 | @pixels : Pointer(UInt8) 10 | 11 | getter width, height, bpp, pixels 12 | 13 | def self.load_file(file) 14 | Image.new file 15 | end 16 | 17 | def initialize(@width, @height, @bpp = 32u32) 18 | @pixels = Pointer(UInt8).malloc byte_size 19 | end 20 | 21 | def initialize(@width, @height, @bpp, @pixels) 22 | end 23 | 24 | def initialize(@width, @height, @bpp, pixels) 25 | @pixels = pixels.pointer 26 | end 27 | 28 | def initialize(file : String) 29 | format = LibFreeImage.getFileType file, 0 30 | native = LibFreeImage.load format, file, 0 31 | raise Error.new "Failed to load image #{file}" if native.null? 32 | initialize(native) 33 | LibFreeImage.unload native 34 | end 35 | 36 | def size 37 | Vector2u.new(@width, @height) 38 | end 39 | 40 | def byte_size : UInt64 41 | width.to_u64 * height.to_u64 * bpp.to_u64 42 | end 43 | 44 | def clone 45 | self.class.new @width, @height, @bpp, @pixels.clone 46 | end 47 | 48 | def update(x, y, w, h, color : Colori) 49 | update IntRect.new(x, y, w, h), color 50 | end 51 | 52 | def update(rect, color : Colori) 53 | rect.height.times do |y| 54 | rect.width.times do |x| 55 | set_pixel rect.left + x, rect.top + y, color 56 | end 57 | end 58 | end 59 | 60 | requires x >= 0 && x < @width 61 | requires y >= 0 && y < @height 62 | def set_pixel(x, y, color : Colori) 63 | index = (x + y * width) * (@bpp // 8) 64 | @pixels[index + 0] = color.r if @bpp >= 8 65 | @pixels[index + 1] = color.g if @bpp >= 16 66 | @pixels[index + 2] = color.b if @bpp >= 24 67 | @pixels[index + 3] = color.a if @bpp >= 32 68 | end 69 | 70 | requires x >= 0 && x < @width 71 | requires y >= 0 && y < @height 72 | def set_pixel(x, y, color : Colorf) 73 | converted = (color * 255).to_u8 74 | set_pixel x, y, converted 75 | end 76 | 77 | requires x >= 0 && x < @width 78 | requires y >= 0 && y < @height 79 | def get_pixel(x, y) 80 | r, g, b, a = 0u8, 0u8, 0u8, 0u8 81 | index = (x + y * width) * (@bpp // 8) 82 | r = @pixels[index + 0] if @bpp >= 8 83 | g = @pixels[index + 1] if @bpp >= 16 84 | b = @pixels[index + 2] if @bpp >= 24 85 | a = @pixels[index + 3] if @bpp >= 32 86 | Colori.new r, g, b, a 87 | end 88 | 89 | def fill(color : Colori) 90 | update 0, 0, width.to_i, height.to_i, color 91 | end 92 | 93 | protected def initialize(native : LibFreeImage::FIBITMAP*) 94 | @width = LibFreeImage.getWidth native 95 | @height = LibFreeImage.getHeight native 96 | @bpp = LibFreeImage.getBPP native 97 | 98 | ptr = LibFreeImage.getBits native 99 | pitch = LibFreeImage.getPitch native 100 | 101 | @pixels = Pointer(UInt8).malloc byte_size 102 | 103 | bytes = 0 104 | @height.times do |y| 105 | pixel = ptr 106 | @width.times do |x| 107 | case @bpp 108 | when 16; convert_16bit pixel, bytes 109 | when 24; convert_24bit pixel, bytes 110 | when 32; convert_32bit pixel, bytes 111 | end 112 | pixel += @bpp // 8 113 | bytes += @bpp // 8 114 | end 115 | ptr += pitch 116 | end 117 | end 118 | 119 | private def convert_16bit(pixel, index) : Void 120 | red = pixel[LibFreeImage::RGBA_RED] 121 | green = pixel[LibFreeImage::RGBA_GREEN] 122 | @pixels[index + 0] = red 123 | @pixels[index + 1] = green 124 | end 125 | 126 | private def convert_24bit(pixel, index) : Void 127 | red = pixel[LibFreeImage::RGBA_RED] 128 | green = pixel[LibFreeImage::RGBA_GREEN] 129 | blue = pixel[LibFreeImage::RGBA_BLUE] 130 | @pixels[index + 0] = red 131 | @pixels[index + 1] = green 132 | @pixels[index + 2] = blue 133 | end 134 | 135 | private def convert_32bit(pixel, index) : Void 136 | red = pixel[LibFreeImage::RGBA_RED] 137 | green = pixel[LibFreeImage::RGBA_GREEN] 138 | blue = pixel[LibFreeImage::RGBA_BLUE] 139 | alpha = pixel[LibFreeImage::RGBA_ALPHA] 140 | @pixels[index + 0] = red 141 | @pixels[index + 1] = green 142 | @pixels[index + 2] = blue 143 | @pixels[index + 3] = alpha 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /src/boleite/graphics/line_shape.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Drawable 2 | end 3 | 4 | module Boleite::Transformable 5 | end 6 | 7 | class Boleite::LineShape 8 | include Drawable 9 | include Transformable 10 | 11 | @shape = Shape.new Primitive::LinesStrip 12 | 13 | delegate :color, :color=, to: @shape 14 | 15 | def initialize 16 | end 17 | 18 | def add(x, y) 19 | @shape.add_vertex x, y 20 | end 21 | 22 | def add(pos) 23 | add pos.x, pos.y 24 | end 25 | 26 | def num_points 27 | @shape.num_vertices 28 | end 29 | 30 | def num_lines 31 | 1 + (@shape.num_vertices - 1) / 2 32 | end 33 | 34 | private def internal_render(renderer, transform) 35 | transform = Matrix.mul transform, self.transformation 36 | renderer.draw @shape, transform 37 | end 38 | end -------------------------------------------------------------------------------- /src/boleite/graphics/partial_circle_shape.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Drawable 2 | end 3 | 4 | module Boleite::Transformable 5 | end 6 | 7 | class Boleite::PartialCircleShape 8 | include Drawable 9 | include Transformable 10 | 11 | @rebuild = true 12 | @shape = Shape.new Primitive::TriangleFan 13 | 14 | getter radius, degrees 15 | getter quality = 32 16 | 17 | delegate :color, :color=, to: @shape 18 | 19 | def initialize(@radius = 1.0, @degrees = 360.0) 20 | end 21 | 22 | def radius=(@radius) 23 | @rebuild = true 24 | end 25 | 26 | def degrees=(@degrees) 27 | @rebuild = true 28 | end 29 | 30 | def quality=(@quality) 31 | @rebuild = true 32 | end 33 | 34 | private def internal_render(renderer, transform) 35 | build_circle if @rebuild 36 | transform = Matrix.mul transform, self.transformation 37 | renderer.draw @shape, transform 38 | end 39 | 40 | def build_circle 41 | @shape.clear_vertices 42 | @shape.add_vertex Boleite::Vector2f32.zero 43 | step = -@degrees * (Math::PI / 180.0) / @quality 44 | (0..@quality).each do |index| 45 | point1 = Boleite::Vector2f.new @radius * Math.cos(index * step), @radius * Math.sin(index * step) 46 | point2 = Boleite::Vector2f.new @radius * Math.cos((index + 1) * step), @radius * Math.sin((index + 1) * step) 47 | 48 | if @degrees > 0 # Positive direction 49 | @shape.add_vertex point1.to_f32 50 | @shape.add_vertex point2.to_f32 51 | else 52 | @shape.add_vertex point2.to_f32 53 | @shape.add_vertex point1.to_f32 54 | end 55 | end 56 | @rebuild = false 57 | end 58 | end -------------------------------------------------------------------------------- /src/boleite/graphics/render_target.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::RenderTarget 2 | abstract def width : UInt32 3 | abstract def height : UInt32 4 | abstract def size : Vector2u 5 | end 6 | -------------------------------------------------------------------------------- /src/boleite/graphics/renderer.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::DrawCallContext 2 | property vertices, transformation, shader 3 | property uniforms, buffers 4 | 5 | @vertices : VertexBufferObject 6 | @transformation = Matrix44f32.identity 7 | @shader = nil 8 | @uniforms = DrawCallUniforms.new 9 | @buffers = [] of VertexBuffer 10 | 11 | def initialize(@vertices : VertexBufferObject, @transformation = Matrix44f32.identity) 12 | end 13 | 14 | def initialize(@vertices : VertexBufferObject, @shader : Shader, @transformation = Matrix44f32.identity) 15 | end 16 | end 17 | 18 | struct Boleite::DrawCallUniforms 19 | alias Value = Float32 | Vector2f32 | Vector3f32 | Vector4f32 | Matrix33f32 | Matrix44f32 | Texture 20 | 21 | @values = {} of String => Value 22 | 23 | def []=(name, value) 24 | @values[name] = value 25 | end 26 | 27 | def apply_to(shader) 28 | @values.each do |key, value| 29 | apply_value shader, key, value 30 | end 31 | end 32 | 33 | protected def apply_value(shader, key, value) 34 | shader.set_parameter(key, value) 35 | end 36 | end 37 | 38 | abstract class Boleite::Renderer 39 | property camera 40 | getter gfx 41 | 42 | def initialize(@gfx : GraphicsContext, @camera : Camera) 43 | end 44 | 45 | def draw(drawable : Drawable, transform = Matrix44f32.identity) 46 | drawable.render(self, transform) 47 | end 48 | 49 | abstract def clear(color : Colorf) : Void 50 | abstract def draw(drawcall : DrawCallContext) : Void 51 | abstract def present : Void 52 | 53 | protected def apply_shader_settings(shader, world, uniforms) 54 | shader.world_transform = world 55 | shader.view_transform = @camera.inverse_transformation 56 | shader.projection_transform = @camera.projection 57 | uniforms.apply_to shader 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /src/boleite/graphics/shader.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Shader 2 | def self.load_file(path, graphics) 3 | source = File.read(path) 4 | load_string(source, graphics) 5 | end 6 | 7 | def self.load_string(source, graphics) 8 | parser = ShaderParser.new 9 | parser.parse(source) 10 | 11 | graphics.create_shader(parser) 12 | end 13 | 14 | abstract def initialize(parser : ShaderParser) 15 | abstract def activate(&block) 16 | 17 | abstract def set_parameter(name, value : Float32) : Void 18 | abstract def set_parameter(name, value : Vector2f32) : Void 19 | abstract def set_parameter(name, value : Vector3f32) : Void 20 | abstract def set_parameter(name, value : Vector4f32) : Void 21 | abstract def set_parameter(name, value : Matrix33f32) : Void 22 | abstract def set_parameter(name, value : Matrix44f32) : Void 23 | abstract def set_parameter(name, value : Texture) : Void 24 | 25 | abstract def world_transform=(value : Matrix44f32) : Void 26 | abstract def view_transform=(value : Matrix44f32) : Void 27 | abstract def projection_transform=(value : Matrix44f32) : Void 28 | 29 | abstract def has_world_transform?() : Bool 30 | abstract def has_view_transform?() : Bool 31 | abstract def has_projection_transform?() : Bool 32 | end 33 | -------------------------------------------------------------------------------- /src/boleite/graphics/shape.cr: -------------------------------------------------------------------------------- 1 | require "./shape/vertices.cr" 2 | 3 | module Boleite::Transformable 4 | end 5 | 6 | class Boleite::Shape 7 | include Drawable 8 | include Transformable 9 | 10 | @vertices = Vertices.new 11 | @color = Color.white 12 | @custom_shader : Shader? 13 | 14 | property color, custom_shader 15 | delegate primitive, to: @vertices 16 | 17 | def initialize() 18 | end 19 | 20 | def initialize(p : Primitive) 21 | @vertices.primitive = p 22 | end 23 | 24 | def primitive=(p) 25 | @vertices.primitive = p 26 | end 27 | 28 | def add_vertex(x, y) 29 | add_vertex Vector2f32.new(x.to_f32, y.to_f32) 30 | end 31 | 32 | def add_vertex(pos) 33 | @vertices.add pos 34 | end 35 | 36 | def []=(index, pos) 37 | @vertices.set index, pos 38 | end 39 | 40 | def [](index) : Vector2f32 41 | @vertices.get index 42 | end 43 | 44 | def num_vertices 45 | @vertices.size 46 | end 47 | 48 | def clear_vertices 49 | @vertices.clear 50 | end 51 | 52 | protected def internal_render(renderer, transform) 53 | vertices = @vertices.get_vertices(renderer.gfx) 54 | unless shader = @custom_shader 55 | shader = @vertices.get_shader(renderer.gfx) 56 | end 57 | transform = Matrix.mul transform, self.transformation 58 | drawcall = DrawCallContext.new vertices, shader, transform 59 | drawcall.uniforms["color"] = @color 60 | renderer.draw drawcall 61 | end 62 | end -------------------------------------------------------------------------------- /src/boleite/graphics/shape/shape.shader: -------------------------------------------------------------------------------- 1 | #version 450 2 | values 3 | { 4 | worldTransform = world; 5 | viewTransform = camera; 6 | projectionTransform = projection; 7 | } 8 | 9 | depth 10 | { 11 | enabled = false; 12 | function = Always; 13 | } 14 | 15 | blend 16 | { 17 | enabled = true; 18 | function = Add; 19 | sourceFactor = SourceAlpha; 20 | destinationFactor = OneMinusSourceAlpha; 21 | } 22 | 23 | vertex 24 | { 25 | layout(location = 0) in vec2 position; 26 | uniform mat4 world; 27 | uniform mat4 camera; 28 | uniform mat4 projection; 29 | void main() 30 | { 31 | vec4 worldPos = world * vec4(position, 0, 1); 32 | vec4 viewPos = camera * worldPos; 33 | gl_Position = projection * viewPos; 34 | //gl_Position = viewPos; 35 | } 36 | } 37 | 38 | fragment 39 | { 40 | layout(location = 0) out vec4 outputColor; 41 | uniform vec4 color; 42 | void main() 43 | { 44 | outputColor = color; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/boleite/graphics/shape/vertices.cr: -------------------------------------------------------------------------------- 1 | abstract struct Boleite::Vertex 2 | end 3 | 4 | class Boleite::Shape 5 | struct Vertex < Vertex 6 | @pos : Vector2f32 7 | 8 | getter pos 9 | 10 | def initialize(@pos) 11 | end 12 | end 13 | 14 | struct Vertices 15 | @@shader : Shader? 16 | 17 | @vbo : VertexBufferObject? 18 | @vertices = [] of Vertex 19 | @rebuild = true 20 | @primitive = Primitive::Triangles 21 | 22 | def add(pos) 23 | @vertices << Vertex.new pos 24 | @rebuild = true 25 | end 26 | 27 | def set(index, pos) 28 | @vertices[index] = Vertex.new pos 29 | @rebuild = true 30 | end 31 | 32 | def get(index) 33 | @vertices[index].pos 34 | end 35 | 36 | def size 37 | @vertices.size 38 | end 39 | 40 | def clear 41 | @vertices.clear 42 | @rebuild = true 43 | end 44 | 45 | def primitive 46 | @primitive 47 | end 48 | 49 | def primitive=(p) 50 | @primitive = p 51 | if vbo = @vbo 52 | vbo.primitive = @primitive 53 | end 54 | end 55 | 56 | def get_vertices(gfx) : VertexBufferObject 57 | vbo = @vbo 58 | if vbo.nil? 59 | vbo = create_vertices(gfx) 60 | @vbo = vbo 61 | end 62 | if @rebuild 63 | update_vertices vbo 64 | @rebuild = false 65 | end 66 | vbo 67 | end 68 | 69 | def create_vertices(gfx) : VertexBufferObject 70 | layout = VertexLayout.new [ 71 | VertexAttribute.new(0, 2, :float, 8u32, 0u32, 0u32), 72 | ] 73 | vbo = gfx.create_vertex_buffer_object 74 | vbo.layout = layout 75 | vbo.primitive = @primitive 76 | vbo.create_buffer 77 | vbo 78 | end 79 | 80 | def get_shader(gfx) : Shader 81 | shader = @@shader 82 | if shader.nil? 83 | shader = create_shader(gfx) 84 | @@shader = shader 85 | end 86 | shader 87 | end 88 | 89 | def create_shader(gfx) : Shader 90 | source = {{`cat #{__DIR__}/shape.shader`.stringify }} 91 | parser = ShaderParser.new 92 | parser.parse source 93 | gfx.create_shader(parser) 94 | end 95 | 96 | def update_vertices(vbo) 97 | buffer = vbo.get_buffer(0) 98 | buffer.clear 99 | @vertices.each do |vertex| 100 | buffer.add_data vertex 101 | end 102 | end 103 | end 104 | end -------------------------------------------------------------------------------- /src/boleite/graphics/sprite.cr: -------------------------------------------------------------------------------- 1 | require "./sprite/vertices.cr" 2 | 3 | module Boleite::Transformable 4 | end 5 | 6 | class Boleite::Sprite 7 | include Drawable 8 | include Transformable 9 | 10 | getter texture, texture_rect, size 11 | property color 12 | 13 | @size : Vector2u 14 | @vertices = Vertices.new 15 | @color = Color.white 16 | 17 | def initialize(@texture : Texture) 18 | @size = @texture.size 19 | @texture_rect = IntRect.new(0, 0, @size.x.to_i, @size.y.to_i) 20 | @vertices.update_uv_vertices @size, @texture_rect 21 | end 22 | 23 | def texture=(@texture) 24 | @size = @texture.size 25 | @texture_rect = IntRect.new(0, 0, @size.x.to_i, @size.y.to_i) 26 | @vertices.update_uv_vertices @size, @texture_rect 27 | end 28 | 29 | def texture_rect=(rect) 30 | @texture_rect = rect 31 | @vertices.update_uv_vertices @texture.size, @texture_rect 32 | end 33 | 34 | def size=(size) 35 | @size = size 36 | @vertices.update_uv_vertices @texture.size, @texture_rect 37 | end 38 | 39 | protected def internal_render(renderer, transform) 40 | vertices = @vertices.get_vertices(renderer.gfx) 41 | shader = @vertices.get_shader(renderer.gfx) 42 | uv = @vertices.get_uv(renderer.gfx) 43 | scale_transform = Matrix.scale Matrix44f32.identity, Vector4f32.new(@size.x.to_f32, @size.y.to_f32, 1f32, 1f32) 44 | transform = Matrix.mul transform, self.transformation 45 | transform = Matrix.mul scale_transform, transform 46 | drawcall = DrawCallContext.new vertices, shader, transform 47 | drawcall.uniforms["colorTexture"] = texture 48 | drawcall.uniforms["modulateColor"] = color 49 | drawcall.buffers << uv 50 | renderer.draw drawcall 51 | end 52 | end -------------------------------------------------------------------------------- /src/boleite/graphics/sprite/sprite.shader: -------------------------------------------------------------------------------- 1 | #version 450 2 | values 3 | { 4 | worldTransform = world; 5 | viewTransform = camera; 6 | projectionTransform = projection; 7 | } 8 | 9 | depth 10 | { 11 | enabled = false; 12 | function = Always; 13 | } 14 | 15 | blend 16 | { 17 | enabled = true; 18 | function = Add; 19 | sourceFactor = SourceAlpha; 20 | destinationFactor = OneMinusSourceAlpha; 21 | } 22 | 23 | vertex 24 | { 25 | layout(location = 0) in vec2 position; 26 | layout(location = 1) in vec2 uv; 27 | uniform mat4 world; 28 | uniform mat4 camera; 29 | uniform mat4 projection; 30 | out VertexData { 31 | vec2 uv; 32 | } outputVertex; 33 | void main() 34 | { 35 | vec4 worldPos = world * vec4(position, 0, 1); 36 | vec4 viewPos = camera * worldPos; 37 | gl_Position = projection * viewPos; 38 | outputVertex.uv = uv; 39 | } 40 | } 41 | 42 | fragment 43 | { 44 | layout(location = 0) out vec4 outputColor; 45 | uniform sampler2D colorTexture; 46 | uniform vec4 modulateColor; 47 | in VertexData { 48 | vec2 uv; 49 | } inputVertex; 50 | void main() 51 | { 52 | vec4 color = texture(colorTexture, inputVertex.uv); 53 | outputColor = color * modulateColor; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/boleite/graphics/sprite/vertices.cr: -------------------------------------------------------------------------------- 1 | abstract struct Boleite::Vertex 2 | end 3 | 4 | class Boleite::Sprite 5 | struct Vertex < Vertex 6 | @data = Vector2f32.zero 7 | 8 | def initialize(x, y) 9 | @data = Vector2f32.new(x, y) 10 | end 11 | end 12 | 13 | struct Vertices 14 | @@vertices : VertexBufferObject? 15 | @@shader : Shader? 16 | 17 | @uv_buffer : VertexBuffer? 18 | @uv_vertices = StaticArray(Vertex, 4).new(Vertex.new(0f32, 0f32)) 19 | 20 | def get_vertices(gfx) : VertexBufferObject 21 | vertices = @@vertices 22 | if vertices.nil? 23 | vertices = create_vertices(gfx) 24 | @@vertices = vertices 25 | end 26 | vertices 27 | end 28 | 29 | def get_uv(gfx) : VertexBuffer 30 | buffer = @uv_buffer 31 | if buffer.nil? 32 | buffer = gfx.create_vertex_buffer 33 | @uv_buffer = buffer 34 | end 35 | buffer.clear 36 | @uv_vertices.each { |uv| buffer.add_data uv } 37 | buffer 38 | end 39 | 40 | def create_vertices(gfx) : VertexBufferObject 41 | vertices = [ 42 | Vertex.new(0.0f32, 0.0f32), 43 | Vertex.new(0.0f32, 1.0f32), 44 | Vertex.new(1.0f32, 0.0f32), 45 | Vertex.new(1.0f32, 1.0f32), 46 | ] 47 | 48 | layout = VertexLayout.new [ 49 | VertexAttribute.new(0, 2, :float, 8_u32, 0_u32, 0_u32), 50 | VertexAttribute.new(1, 2, :float, 8_u32, 0_u32, 0_u32), 51 | ] 52 | vbo = gfx.create_vertex_buffer_object 53 | vbo.layout = layout 54 | vbo.primitive = Primitive::TrianglesStrip 55 | buffer = vbo.create_buffer 56 | vertices.each { |vertex| buffer.add_data vertex } 57 | vbo 58 | end 59 | 60 | def get_shader(gfx) : Shader 61 | shader = @@shader 62 | if shader.nil? 63 | shader = create_shader(gfx) 64 | @@shader = shader 65 | end 66 | shader 67 | end 68 | 69 | def create_shader(gfx) : Shader 70 | source = {{`cat #{__DIR__}/sprite.shader`.stringify }} 71 | parser = ShaderParser.new 72 | parser.parse source 73 | gfx.create_shader(parser) 74 | end 75 | 76 | def update_uv_vertices(size, rect) 77 | size = size.to_f32 78 | tex_min, tex_max = rect.bounds 79 | tex_min = tex_min.to_f32 / size 80 | tex_max = tex_max.to_f32 / size 81 | 82 | @uv_vertices[0] = Vertex.new(tex_min.x, tex_max.y) 83 | @uv_vertices[1] = Vertex.new(tex_min.x, tex_min.y) 84 | @uv_vertices[2] = Vertex.new(tex_max.x, tex_max.y) 85 | @uv_vertices[3] = Vertex.new(tex_max.x, tex_min.y) 86 | end 87 | end 88 | end -------------------------------------------------------------------------------- /src/boleite/graphics/text.cr: -------------------------------------------------------------------------------- 1 | require "./text/*" 2 | 3 | module Boleite::Transformable 4 | end 5 | 6 | class Boleite::Text 7 | include Drawable 8 | include Transformable 9 | 10 | class Line 11 | getter glyphs, text 12 | def initialize(@glyphs : Array(Font::Glyph), @text : String) 13 | end 14 | end 15 | 16 | property font 17 | getter text, size, default_color, formatter 18 | 19 | 20 | @font : Font 21 | @text : String 22 | @lines = [] of Line 23 | @size = 12u32 24 | @default_color = Color.white 25 | @formatter = Formatter.new 26 | @vertices = Vertices.new 27 | 28 | def initialize(@font, @text = "") 29 | find_glyphs 30 | end 31 | 32 | def text=(val) 33 | @text = val 34 | @vertices.mark_for_rebuild 35 | find_glyphs 36 | end 37 | 38 | def size=(val) 39 | @size = val 40 | @vertices.mark_for_rebuild 41 | find_glyphs 42 | end 43 | 44 | def default_color=(val) 45 | if val != @default_color 46 | @vertices.mark_for_rebuild 47 | end 48 | @default_color = val 49 | end 50 | 51 | protected def internal_render(renderer, transform) 52 | vertices = @vertices.get_vertices renderer.gfx, TextData.new(@font, @size, @lines, @formatter, @default_color) 53 | shader = @vertices.get_shader renderer.gfx 54 | transform = Matrix.mul transform, self.transformation 55 | drawcall = DrawCallContext.new vertices, shader, transform 56 | drawcall.uniforms["fontTexture"] = @font.texture_for @size 57 | renderer.draw drawcall 58 | end 59 | 60 | private def find_glyphs 61 | glyph_line = [] of Font::Glyph 62 | line = "" 63 | @lines.clear 64 | @text.each_char do |char| 65 | if char == '\n' 66 | @lines << Line.new glyph_line, line 67 | glyph_line = [] of Font::Glyph 68 | line = "" 69 | else 70 | glyph_line << @font.get_glyph char, @size 71 | line += char 72 | end 73 | end 74 | @lines << Line.new glyph_line, line unless glyph_line.empty? 75 | end 76 | end -------------------------------------------------------------------------------- /src/boleite/graphics/text/format_rule.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Text 2 | struct FormatRule 3 | @rule : Regex | Range(Int32, Int32) | String 4 | @color : Colorf 5 | 6 | def initialize(@rule, @color) 7 | end 8 | 9 | def apply_rule(text, colors) 10 | case @rule 11 | when Regex 12 | apply_regex_rule @rule.as(Regex), text, colors 13 | when Range(UInt32, UInt32) 14 | apply_range_rule text, colors 15 | when String 16 | apply_keyword_rule text, colors 17 | end 18 | end 19 | 20 | def apply_regex_rule(regex, text, colors) 21 | pos = 0 22 | while match = regex.match text, pos 23 | match.group_size.times do |i| 24 | start, stop = match.begin(i), match.end(i) 25 | if start && stop 26 | (start...stop).each do |p| 27 | colors[p] = @color 28 | end 29 | pos = stop if stop > pos 30 | else 31 | pos = text.size 32 | end 33 | end 34 | pos = text.size if match.group_size <= 0 35 | end 36 | end 37 | 38 | def apply_range_rule(text, colors) 39 | range = @rule.as(Range(Int32, Int32)) 40 | range.each do |i| 41 | colors[i] = @color 42 | end 43 | end 44 | 45 | def apply_keyword_rule(text, colors) 46 | keyword = @rule.as(String) 47 | apply_regex_rule(/(#{keyword})/, text, colors) 48 | end 49 | end 50 | end -------------------------------------------------------------------------------- /src/boleite/graphics/text/formatter.cr: -------------------------------------------------------------------------------- 1 | class Boleite::Text 2 | class Formatter 3 | @rules = [] of FormatRule 4 | 5 | def add(rule, color) : self 6 | @rules << FormatRule.new rule, color 7 | self 8 | end 9 | 10 | def format(text, default) : Array(Colorf) 11 | colors = Array.new text.size, default 12 | @rules.each do |rule| 13 | rule.apply_rule text, colors 14 | end 15 | colors 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /src/boleite/graphics/text/text.shader: -------------------------------------------------------------------------------- 1 | #version 450 2 | values 3 | { 4 | worldTransform = world; 5 | viewTransform = camera; 6 | projectionTransform = projection; 7 | } 8 | 9 | depth 10 | { 11 | enabled = false; 12 | function = Always; 13 | } 14 | 15 | blend 16 | { 17 | enabled = true; 18 | function = Add; 19 | sourceFactor = One; 20 | destinationFactor = One; 21 | } 22 | 23 | vertex 24 | { 25 | layout(location = 0) in vec2 position; 26 | layout(location = 1) in vec2 uv; 27 | layout(location = 2) in vec4 color; 28 | uniform mat4 world; 29 | uniform mat4 camera; 30 | uniform mat4 projection; 31 | out VertexData { 32 | vec2 uv; 33 | vec4 color; 34 | } outputVertex; 35 | void main() 36 | { 37 | vec4 worldPos = world * vec4(position, 0, 1); 38 | vec4 viewPos = camera * worldPos; 39 | gl_Position = projection * viewPos; 40 | outputVertex.uv = uv; 41 | outputVertex.color = color; 42 | } 43 | } 44 | 45 | fragment 46 | { 47 | layout(location = 0) out vec4 outputColor; 48 | uniform sampler2D fontTexture; 49 | in VertexData { 50 | vec2 uv; 51 | vec4 color; 52 | } inputVertex; 53 | void main() 54 | { 55 | float mask = texture(fontTexture, inputVertex.uv).r; 56 | outputColor = inputVertex.color * mask; 57 | } 58 | } -------------------------------------------------------------------------------- /src/boleite/graphics/text/vertices.cr: -------------------------------------------------------------------------------- 1 | abstract struct Boleite::Vertex 2 | end 3 | 4 | class Boleite::Text 5 | struct Vertex < Vertex 6 | @pos = Vector2f32.zero 7 | @uv = Vector2f32.zero 8 | @color = Vector4f32.zero 9 | 10 | def initialize(x, y, u, v, @color) 11 | @pos = Vector2f32.new(x, y) 12 | @uv = Vector2f32.new(u, v) 13 | end 14 | end 15 | 16 | struct TextData 17 | getter font, size, lines, formatter, default_color 18 | 19 | @font : Font 20 | @size : UInt32 21 | @lines : Array(Line) 22 | @formatter : Formatter 23 | @default_color : Colorf 24 | 25 | def initialize(@font, @size, @lines, @formatter, @default_color) 26 | end 27 | end 28 | 29 | struct Vertices 30 | @@shader : Shader? 31 | 32 | @vertices : VertexBufferObject? 33 | @rebuild = true 34 | 35 | def mark_for_rebuild 36 | @rebuild = true 37 | end 38 | 39 | def get_vertices(gfx, data) : VertexBufferObject 40 | vertices = @vertices 41 | if @rebuild || vertices.nil? 42 | vertices = create_vertices gfx, data 43 | @vertices = vertices 44 | @rebuild = false 45 | end 46 | vertices 47 | end 48 | 49 | def create_vertices(gfx, data) : VertexBufferObject 50 | vertices = build_vertices gfx, data 51 | layout = VertexLayout.new [ 52 | VertexAttribute.new(0, 2, :float, 32u32, 0u32, 0u32), 53 | VertexAttribute.new(0, 2, :float, 32u32, 8u32, 0u32), 54 | VertexAttribute.new(0, 4, :float, 32u32, 16u32, 0u32), 55 | ] 56 | vbo = gfx.create_vertex_buffer_object 57 | vbo.layout = layout 58 | vbo.primitive = Primitive::Triangles 59 | buffer = vbo.create_buffer 60 | vertices.each { |vertex| buffer.add_data vertex } 61 | vbo 62 | end 63 | 64 | def build_vertices(gfx, data) : Array(Vertex) 65 | vertices = [] of Vertex 66 | texture_size = data.font.texture_for(data.size).size 67 | baseline = -3 68 | linespacing = data.font.get_linespacing(data.size) 69 | data.lines.each do |line| 70 | baseline += linespacing 71 | colors = data.formatter.format line.text, data.default_color 72 | build_line_vertices data.font, vertices, line, colors, baseline, texture_size, data.size 73 | end 74 | vertices 75 | end 76 | 77 | def build_line_vertices(font, vertices, line, colors, baseline, texture_size, char_size) 78 | prev_glyph = nil 79 | advance = 0 80 | line.glyphs.zip colors do |glyph, color| 81 | kerning = 0 82 | kerning = font.get_kerning prev_glyph.code, glyph.code, char_size if prev_glyph 83 | create_glyph_vertices vertices, glyph, color, advance, baseline, texture_size 84 | advance += glyph.advance + kerning 85 | prev_glyph = glyph 86 | end 87 | end 88 | 89 | def create_glyph_vertices(vertices, glyph, color, advance, top, texture_size) 90 | top = glyph.bounds.height - top 91 | min, max = glyph.bounds.bounds 92 | min, max = min.to_f32, max.to_f32 93 | tex_min, tex_max = glyph.texture_rect.bounds 94 | tex_min, tex_max = tex_min.to_f / texture_size.to_f, tex_max.to_f / texture_size.to_f 95 | tex_min, tex_max = tex_min.to_f32, tex_max.to_f32 96 | vertices << Vertex.new(min.x + advance, min.y - top, tex_min.x, tex_min.y, color) 97 | vertices << Vertex.new(min.x + advance, max.y - top, tex_min.x, tex_max.y, color) 98 | vertices << Vertex.new(max.x + advance, min.y - top, tex_max.x, tex_min.y, color) 99 | vertices << Vertex.new(min.x + advance, max.y - top, tex_min.x, tex_max.y, color) 100 | vertices << Vertex.new(max.x + advance, max.y - top, tex_max.x, tex_max.y, color) 101 | vertices << Vertex.new(max.x + advance, min.y - top, tex_max.x, tex_min.y, color) 102 | end 103 | 104 | def get_shader(gfx) : Shader 105 | shader = @@shader 106 | if shader.nil? 107 | shader = create_shader(gfx) 108 | @@shader = shader 109 | end 110 | shader 111 | end 112 | 113 | def create_shader(gfx) : Shader 114 | source = {{`cat #{__DIR__}/text.shader`.stringify }} 115 | parser = ShaderParser.new 116 | parser.parse source 117 | gfx.create_shader(parser) 118 | end 119 | end 120 | end -------------------------------------------------------------------------------- /src/boleite/graphics/texture.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Texture 2 | enum Format 3 | Red 4 | RG 5 | RGB 6 | RGBA 7 | end 8 | 9 | enum Type 10 | Integer8 11 | Integer16 12 | Float16 13 | Float32 14 | end 15 | 16 | def self.bpp_to_format(bpp) 17 | case bpp 18 | when 32; Format::RGBA 19 | when 24; Format::RGB 20 | when 16; Format::RG 21 | when 8; Format::Red 22 | else 23 | raise "Unsupported bits per pixel given! (#{bpp})" 24 | end 25 | end 26 | 27 | def self.load_file(file, graphics) : Texture 28 | img = Image.load_file file 29 | load_image img, graphics 30 | end 31 | 32 | def self.load_image(img, graphics) : Texture 33 | texture = graphics.create_texture 34 | texture.create img.width, img.height, Format::RGBA, Type::Integer8 35 | texture.update img 36 | texture 37 | end 38 | 39 | def update(img : Image) : Void 40 | size = img.size 41 | format = Texture.bpp_to_format img.bpp 42 | update img.pixels, size.x, size.y, 0u32, 0u32, format 43 | end 44 | 45 | def update(texture : Texture) : Void 46 | update(texture, 0, 0) 47 | end 48 | 49 | def update(bytes : Bytes, width : UInt32, height : UInt32, x_dest : UInt32, y_dest : UInt32, format : Format) : Void 50 | update bytes.to_unsafe, width, height, x_dest, y_dest, format 51 | end 52 | 53 | abstract def create(width : UInt32, height : UInt32, format : Format, type : Type) : Void 54 | abstract def create_depth(width : UInt32, height : UInt32) : Void 55 | 56 | abstract def update(pixels : Pointer(UInt8), width : UInt32, height : UInt32, x_dest : UInt32, y_dest : UInt32, format : Format) : Void 57 | abstract def update(pixels : Pointer(Float32), width : UInt32, height : UInt32, x_dest : UInt32, y_dest : UInt32, format : Format) : Void 58 | abstract def update(texture : Texture, x : UInt32, y : UInt32) : Void 59 | 60 | abstract def size : Vector2u 61 | 62 | abstract def format : Format 63 | abstract def type : Type 64 | 65 | abstract def is_depth? : Bool 66 | abstract def is_smooth? : Bool 67 | abstract def smooth=(val : Bool) : Bool 68 | 69 | abstract def is_repeating? : Bool 70 | abstract def repeating=(val : Bool) : Bool 71 | 72 | abstract def activate(&block) 73 | end 74 | -------------------------------------------------------------------------------- /src/boleite/graphics/transformable.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Transformable 2 | @update_transform = true 3 | @transform = Matrix44f32.identity 4 | @pos = Vector2f.zero 5 | @origo = Vector2f.zero 6 | @scale = Vector2f.one 7 | @rot = 0.0 8 | 9 | def position 10 | @pos 11 | end 12 | 13 | def position=(pos) 14 | @pos = pos 15 | @update_transform = true 16 | end 17 | 18 | def origo 19 | @origo 20 | end 21 | 22 | def origo=(origo) 23 | @origo = origo 24 | @update_transform = true 25 | end 26 | 27 | def scale 28 | @scale 29 | end 30 | 31 | def scale=(scale) 32 | @scale = scale 33 | @update_transform = true 34 | end 35 | 36 | def rotation 37 | @rot 38 | end 39 | 40 | def rotation=(rot) 41 | @rot = rot 42 | @update_transform = true 43 | end 44 | 45 | def move(offset) 46 | self.position = position + offset 47 | end 48 | 49 | def move(x, y) 50 | self.position = position + Vector2f.new(x, y) 51 | end 52 | 53 | def rotate(rot) 54 | self.rotation = rotation + rot 55 | end 56 | 57 | def transformation 58 | if @update_transform 59 | update_transformation 60 | @update_transform = false 61 | end 62 | @transform 63 | end 64 | 65 | def update_transformation 66 | angle = @rot * Math::PI / 180.0 67 | cosine = Math.cos angle 68 | sine = Math.sin angle 69 | sc = Vector2f.new @scale.x * cosine, @scale.y * cosine 70 | ss = Vector2f.new @scale.x * sine, @scale.y * sine 71 | pos = Vector2f.new -@origo.x * sc.x - @origo.y * ss.y, @origo.x * ss.x - @origo.y * sc.y 72 | pos += @pos 73 | @transform = Matrix44f32.new(*{ 74 | sc.x.to_f32, -ss.x.to_f32, 0f32, 0f32, 75 | ss.y.to_f32, sc.y.to_f32, 0f32, 0f32, 76 | 0f32, 0f32, 1f32, 0f32, 77 | pos.x.to_f32, pos.y.to_f32, 0f32, 1f32, 78 | }) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /src/boleite/graphics/vertex_buffer.cr: -------------------------------------------------------------------------------- 1 | enum Boleite::Primitive 2 | Points 3 | Lines 4 | LinesStrip 5 | Triangles 6 | TrianglesStrip 7 | TriangleFan 8 | end 9 | 10 | abstract struct Boleite::Vertex 11 | end 12 | 13 | struct Boleite::VertexAttribute 14 | enum Type 15 | Float 16 | Double 17 | Int 18 | end 19 | 20 | property buffer 21 | property size 22 | property type 23 | property stride 24 | property offset 25 | property frequency 26 | 27 | def initialize 28 | @buffer = 0 29 | @size = 0 30 | @type = Type::Float 31 | @stride = 0_u32 32 | @offset = 0_u32 33 | @frequency = 0_u32 34 | end 35 | 36 | def initialize(@buffer, @size, @type, @stride, @offset, @frequency) 37 | end 38 | 39 | def initialize(@buffer, @size, type : Symbol, @stride, @offset, @frequency) 40 | @type = case type 41 | when :float 42 | Type::Float 43 | when :double 44 | Type::Double 45 | when :int 46 | Type::Int 47 | else 48 | raise ArgumentError.new("Invalid symbol given! Got #{type}") 49 | end 50 | end 51 | 52 | def type_size : UInt32 53 | size = case @type 54 | when Type::Float 55 | sizeof(Float32) 56 | when Type::Double 57 | sizeof(Float64) 58 | when Type::Int 59 | sizeof(Int32) 60 | else 61 | sizeof(Float32) # Should never happen but 4 bytes should be default 62 | end 63 | size.to_u32 64 | end 65 | end 66 | 67 | struct Boleite::VertexLayout 68 | @attributes = [] of VertexAttribute 69 | 70 | getter :attributes 71 | 72 | def initialize 73 | end 74 | 75 | def initialize(@attributes) 76 | end 77 | 78 | def vertex_size 79 | @attributes.sum do |attribute| 80 | attribute.type_size * attribute.size 81 | end 82 | end 83 | end 84 | 85 | abstract class Boleite::VertexBufferObject 86 | @buffers = [] of VertexBuffer 87 | @tmp_buffers = [] of VertexBuffer 88 | @primitive = Primitive::Triangles 89 | @layout = VertexLayout.new 90 | @update_layout = true 91 | @indices_buffer : Int32? 92 | 93 | property :primitive 94 | 95 | def layout 96 | @layout 97 | end 98 | 99 | def layout=(layout) 100 | @layout = layout 101 | @update_layout = true 102 | end 103 | 104 | def create_buffer() 105 | buffer = activate { allocate_buffer } 106 | attach_buffer buffer 107 | end 108 | 109 | def attach_buffer(buffer, temp = false) 110 | @buffers << buffer 111 | @tmp_buffers << buffer if temp 112 | @update_layout = true 113 | buffer 114 | end 115 | 116 | def get_buffer(index) 117 | @buffers[index] 118 | end 119 | 120 | def set_indices(index) 121 | if tmp = @indices_buffer 122 | @buffers[tmp].set_vertices_target 123 | end 124 | @indices_buffer = index 125 | @buffers[index].set_indices_target 126 | end 127 | 128 | def set_indices(val : Nil) 129 | if tmp = @indices_buffer 130 | @buffers[tmp].set_vertices_target 131 | end 132 | @indices_buffer = nil 133 | end 134 | 135 | def num_vertices 136 | if tmp = @indices_buffer 137 | @buffers[tmp].size / sizeof(Int32) 138 | else 139 | size = @layout.vertex_size 140 | if size <= 0 141 | 0 142 | else 143 | total_buffer_size / size 144 | end 145 | end 146 | end 147 | 148 | def total_buffer_size 149 | @buffers.sum do |buffer| 150 | buffer.size 151 | end 152 | end 153 | 154 | abstract def allocate_buffer : VertexBuffer 155 | abstract def render(instances) 156 | abstract def update_layout 157 | abstract def activate(&block) 158 | 159 | private def clear_tmp_buffers 160 | @layout.attributes.each do |attribute| 161 | buffer = @buffers[attribute.buffer] 162 | if @tmp_buffers.includes? buffer 163 | @buffers.delete buffer 164 | @update_layout = true 165 | end 166 | end 167 | @tmp_buffers.clear 168 | end 169 | end 170 | 171 | abstract class Boleite::VertexBuffer 172 | @data = [] of UInt8 173 | @rebuild = true 174 | 175 | def add_data(vertex : Vertex) : Void 176 | vertex_size = sizeof(typeof(vertex)) 177 | data = pointerof(vertex).as(UInt8*).to_slice(vertex_size) 178 | add_data data 179 | end 180 | 181 | def add_data(index : Int32) : Void 182 | size = sizeof(Int32) 183 | data = pointerof(index).as(UInt8*).to_slice(size) 184 | add_data data 185 | end 186 | 187 | def add_data(index : UInt32) : Void 188 | size = sizeof(UInt32) 189 | data = pointerof(index).as(UInt8*).to_slice(size) 190 | add_data data 191 | end 192 | 193 | def add_data(slice : Slice(UInt8)) : Void 194 | @data.concat slice 195 | @rebuild = true 196 | end 197 | 198 | def clear : Void 199 | @data.clear 200 | end 201 | 202 | def size 203 | @data.size * sizeof(UInt8) 204 | end 205 | 206 | def needs_rebuild? 207 | @rebuild 208 | end 209 | 210 | abstract def set_vertices_target 211 | abstract def set_indices_target 212 | abstract def build 213 | end 214 | -------------------------------------------------------------------------------- /src/boleite/gui/button.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Widget 3 | end 4 | 5 | class Button < Widget 6 | @label = Label.new 7 | 8 | getter label 9 | 10 | Cute.signal click(pos : Vector2f) 11 | 12 | def initialize 13 | super 14 | 15 | @label.orientation = Label::Orientation::Center 16 | @label.parent = self 17 | 18 | state_change.on &->update_label_size 19 | 20 | clicker = Boleite::GUI::WidgetBasicClick.new self, Boleite::Mouse::Left 21 | @input.register_instance clicker, click 22 | end 23 | 24 | def initialize(text, size = Vector2f.zero) 25 | self.initialize 26 | self.label_text = text 27 | self.size = size 28 | end 29 | 30 | def label_text=(text) 31 | @label.text = text 32 | state_change.emit 33 | end 34 | 35 | protected def update_label_size 36 | @label.size = self.size 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /src/boleite/gui/container.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Widget 3 | end 4 | 5 | abstract class Container < Widget 6 | include CrystalClear 7 | 8 | @children = [] of Widget 9 | @min_size = Vector2f.new 0.0, 0.0 10 | @acc_allocation = FloatRect.new 11 | 12 | delegate :"[]", to: @children 13 | getter min_size, acc_allocation 14 | 15 | def initialize 16 | super 17 | 18 | pulse.on { @children.each &.pulse.emit } 19 | 20 | @input.register_instance ContainerInputPass.new(self), ->pass_input_to_children(InputEvent) 21 | end 22 | 23 | requires child.parent.nil? 24 | ensures @children.count child == 1 25 | def add(child) 26 | @children << child 27 | child.parent = self 28 | end 29 | 30 | requires child.parent == self 31 | ensures @children.includes?(child) == false 32 | ensures child.parent != self 33 | def remove(child) 34 | child = @children.delete child 35 | child.parent = nil if child 36 | state_change.emit 37 | child 38 | end 39 | 40 | def find(name) 41 | @children.find do |child| 42 | child.name == name 43 | end 44 | end 45 | 46 | def num_widgets 47 | @children.size 48 | end 49 | 50 | def clear 51 | @children.each do |child| 52 | child.parent = nil 53 | end 54 | @children.clear 55 | state_change.emit 56 | end 57 | 58 | def each_widget 59 | @children.each do |child| 60 | yield child 61 | end 62 | end 63 | 64 | def min_size=(@min_size) 65 | self.size = @min_size if @min_size.x >= size.x || @min_size.y >= size.y 66 | end 67 | 68 | abstract def reset_acc_allocation 69 | abstract def update_acc_allocation 70 | 71 | protected def reset_body_allocation 72 | @allocation.width = @min_size.x 73 | @allocation.height = @min_size.y 74 | end 75 | 76 | protected def update_body_allocation 77 | reset_body_allocation 78 | alloc = self.allocation 79 | self.each_widget { |child| alloc = alloc.merge_relative child.allocation } 80 | @allocation = alloc 81 | end 82 | 83 | protected def on_state_change 84 | update_acc_allocation 85 | update_body_allocation 86 | super 87 | end 88 | 89 | protected def pass_input_to_children(event : InputEvent) 90 | each_widget do |child| 91 | child.input.process event unless event.claimed? 92 | end 93 | end 94 | end 95 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/button.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class ButtonDesign < DesignDrawer 10 | struct ButtonDrawables 11 | property body, border 12 | 13 | @body = Shape.new 14 | @border = Shape.new 15 | end 16 | 17 | @cache = DrawableCache(ButtonDrawables).new do |widget| 18 | ButtonDrawables.new 19 | end 20 | 21 | def render(widget, transform, graphics) 22 | button = widget.as(Button) 23 | drawables = @cache.find widget 24 | update_drawables drawables, button 25 | draw_drawables drawables, transform, graphics 26 | 27 | transform = Matrix.mul transform, drawables.body.transformation 28 | graphics.draw button.label, transform 29 | end 30 | 31 | def update_drawables(drawables, button) 32 | pos = button.position 33 | size = button.size 34 | update_border drawables.border, pos, size, BORDER_SIZE 35 | update_body drawables.body, pos, size, SECONDARY_COLOR 36 | end 37 | 38 | def draw_drawables(drawables, transform, graphics) 39 | graphics.draw drawables.border, transform 40 | graphics.draw drawables.body, transform 41 | end 42 | 43 | def update_body(shape, pos, size, color) 44 | shape.position = pos 45 | shape.color = color 46 | update_vertices shape, size.to_f32 47 | end 48 | 49 | def update_border(shape, pos, size, border) 50 | shape.position = pos - border 51 | shape.color = BORDER_COLOR 52 | shape_size = (size + border * 2.0).to_f32 53 | update_vertices shape, shape_size 54 | end 55 | 56 | def should_update_vertices?(shape, size) 57 | shape.num_vertices < 6 || shape[3].x != size.x || shape[3].y != size.y 58 | end 59 | 60 | def update_vertices(shape, size) 61 | if should_update_vertices? shape, size 62 | shape.clear_vertices 63 | shape.add_vertex 0, 0 64 | shape.add_vertex 0, size.y 65 | shape.add_vertex size.x, 0 66 | shape.add_vertex size 67 | shape.add_vertex size.x, 0 68 | shape.add_vertex 0, size.y 69 | end 70 | end 71 | end 72 | end 73 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/cache.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Design 3 | end 4 | 5 | class DefaultDesign < Design 6 | class DrawableCache(T) 7 | PURGE_TARGET = 1000 8 | 9 | class DrawableData(T) 10 | @id : UInt64 11 | @ref : WeakRef(Widget) 12 | @drawable : T 13 | 14 | getter id, ref, drawable 15 | 16 | def initialize(@id, @ref, @drawable) 17 | end 18 | end 19 | 20 | @drawables = [] of DrawableData(T) 21 | @counter = 0 22 | 23 | def initialize() 24 | @allocator = ->(widget : Widget) { T.new } 25 | end 26 | 27 | def initialize(@allocator : Proc(Widget, T)) 28 | end 29 | 30 | def initialize(&block : Widget -> T) 31 | @allocator = block 32 | end 33 | 34 | def find(widget) 35 | @counter += 1 36 | purge if @counter >= PURGE_TARGET 37 | find_ref widget 38 | end 39 | 40 | def find_ref(widget) 41 | id = widget.object_id 42 | index = @drawables.bsearch_index { |x| x.id >= id } 43 | if index 44 | data = @drawables[index] 45 | return data.drawable if data.id == id 46 | data = DrawableData(T).new id, WeakRef.new(widget.as(Widget)), @allocator.call(widget) 47 | @drawables.insert index, data 48 | return data.drawable 49 | else 50 | data = DrawableData(T).new id, WeakRef.new(widget.as(Widget)), @allocator.call(widget) 51 | @drawables.push data 52 | return data.drawable 53 | end 54 | end 55 | 56 | def purge 57 | @drawables.clear 58 | @counter = 0 59 | end 60 | end 61 | end 62 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/container.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | abstract class ContainerDesign < DesignDrawer 10 | def render_children(widget, transform, graphics) 11 | container = widget.as(Container) 12 | container.each_widget do |child| 13 | graphics.draw child, transform 14 | end 15 | end 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/design.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Design 3 | end 4 | 5 | class DefaultDesign < Design 6 | PRIMARY_COLOR = Colorf.new 0.273f32, 0.273f32, 0.195f32, 1f32 7 | SECONDARY_COLOR = Colorf.new 0.195f32, 0.195f32, 0.117f32, 1f32 8 | BORDER_COLOR = Colorf.new 0.078f32, 0.078f32, 0.078f32, 1f32 9 | BORDER_SIZE = 1.0 10 | 11 | @font : Font 12 | 13 | def initialize(gfx, @font) 14 | @window = WindowDesign.new 15 | @label = LabelDesign.new @font 16 | @text_box = TextBoxDesign.new @font 17 | @button = ButtonDesign.new 18 | @layout = LayoutDesign.new 19 | @input_field = InputFieldDesign.new 20 | @desktop = DesktopDesign.new 21 | @image = ImageDesign.new gfx 22 | end 23 | 24 | def get_drawer(window : Window) 25 | @window 26 | end 27 | 28 | def get_drawer(label : Label) 29 | @label 30 | end 31 | 32 | def get_drawer(box : TextBox) 33 | @text_box 34 | end 35 | 36 | def get_drawer(button : Button) 37 | @button 38 | end 39 | 40 | def get_drawer(layout : Layout) 41 | @layout 42 | end 43 | 44 | def get_drawer(layout : GridLayout) 45 | @layout 46 | end 47 | 48 | def get_drawer(field : InputField) 49 | @input_field 50 | end 51 | 52 | def get_drawer(desktop : Desktop) 53 | @desktop 54 | end 55 | 56 | def get_drawer(image : Image) 57 | @image 58 | end 59 | end 60 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/desktop.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class DesktopDesign < ContainerDesign 10 | def render(widget, transform, graphics) 11 | desktop = widget.as(Desktop) 12 | pos2d = desktop.position.to_f32 13 | pos3d = Boleite::Vector3f32.new pos2d.x, pos2d.y, 0f32 14 | transform = Matrix.translate transform, pos3d 15 | render_children widget, transform, graphics 16 | end 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/image.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class ImageDesign < DesignDrawer 10 | @undefined : Texture 11 | 12 | def initialize(gfx) 13 | image = Boleite::Image.new 32u32, 32u32, 32u32 14 | image.update 0, 0, 32, 32, (Color.pink * 255.0).to_u8 15 | @undefined = gfx.create_texture 16 | @undefined.create image.width, image.height, Texture::Format::RGBA, Texture::Type::Integer8 17 | @undefined.update image 18 | @cache = DrawableCache(Sprite).new do |widget| 19 | Sprite.new @undefined 20 | end 21 | end 22 | 23 | def render(widget, transform, graphics) 24 | image = widget.as(Image) 25 | sprite = @cache.find image 26 | update_sprite sprite, image 27 | draw_sprite sprite, transform, graphics 28 | end 29 | 30 | def update_sprite(sprite, image) 31 | sprite.position = image.position 32 | if texture = image.texture 33 | sprite.texture = texture 34 | else 35 | sprite.texture = @undefined 36 | end 37 | sprite.size = image.size.to_u32 38 | end 39 | 40 | def draw_sprite(sprite, transform, graphics) 41 | graphics.draw sprite, transform 42 | end 43 | end 44 | end 45 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/input_field.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class InputFieldDesign < DesignDrawer 10 | struct InputFieldDrawables 11 | property body, border 12 | 13 | @body = Shape.new 14 | @border = Shape.new 15 | end 16 | 17 | @cache = DrawableCache(InputFieldDrawables).new do |widget| 18 | InputFieldDrawables.new 19 | end 20 | 21 | def render(widget, transform, graphics) 22 | field = widget.as(InputField) 23 | drawables = @cache.find widget 24 | update_drawables drawables, field 25 | draw_drawables drawables, transform, graphics 26 | 27 | transform = Matrix.mul transform, drawables.body.transformation 28 | graphics.draw field.label, transform 29 | end 30 | 31 | def update_drawables(drawables, field) 32 | pos = field.position 33 | size = field.size 34 | update_border drawables.border, pos, size, BORDER_SIZE 35 | update_body drawables.body, pos, size, SECONDARY_COLOR 36 | end 37 | 38 | def draw_drawables(drawables, transform, graphics) 39 | graphics.draw drawables.border, transform 40 | graphics.draw drawables.body, transform 41 | end 42 | 43 | def update_body(shape, pos, size, color) 44 | shape.position = pos 45 | shape.color = color 46 | update_vertices shape, size.to_f32 47 | end 48 | 49 | def update_border(shape, pos, size, border) 50 | shape.position = pos - border 51 | shape.color = BORDER_COLOR 52 | shape_size = (size + border * 2.0).to_f32 53 | update_vertices shape, shape_size 54 | end 55 | 56 | def should_update_vertices?(shape, size) 57 | shape.num_vertices < 6 || shape[3].x != size.x || shape[3].y != size.y 58 | end 59 | 60 | def update_vertices(shape, size) 61 | if should_update_vertices? shape, size 62 | shape.clear_vertices 63 | shape.add_vertex 0, 0 64 | shape.add_vertex 0, size.y 65 | shape.add_vertex size.x, 0 66 | shape.add_vertex size 67 | shape.add_vertex size.x, 0 68 | shape.add_vertex 0, size.y 69 | end 70 | end 71 | end 72 | end 73 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/label.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class LabelDesign < DesignDrawer 10 | def initialize(@font : Font) 11 | @cache = DrawableCache(Text).new do |widget| 12 | Text.new @font 13 | end 14 | end 15 | 16 | def render(widget, transform, graphics) 17 | label = widget.as(Label) 18 | text = @cache.find label 19 | update_text text, label 20 | draw_text text, transform, graphics 21 | end 22 | 23 | def update_text(text, label) 24 | string = insert_cursor label 25 | string, screen_size = crop_text string, label 26 | pos = calc_text_pos label, screen_size 27 | text.text = string 28 | text.size = label.character_size 29 | text.position = pos 30 | text.default_color = label.color 31 | end 32 | 33 | def draw_text(text, transform, graphics) 34 | graphics.draw text, transform 35 | end 36 | 37 | def insert_cursor(label) 38 | string = label.text.dup 39 | if label.use_cursor? 40 | string = string.insert label.cursor_position, '|' 41 | end 42 | string 43 | end 44 | 45 | def crop_text(string, label) 46 | size = 0.0 47 | character_size = label.character_size 48 | string.each_char_with_index do |char, index| 49 | glyph = @font.get_glyph char, character_size 50 | size += glyph.advance 51 | if !label.use_cursor? && size > label.size.x && index > 3 52 | if index > 3 53 | string = string[0, index-3] + "..." 54 | else 55 | string = "..." 56 | end 57 | break 58 | end 59 | end 60 | return string, size 61 | end 62 | 63 | def calc_text_pos(label, screen_size) 64 | pos = label.position 65 | case label.orientation 66 | when Label::Orientation::Center 67 | pos = label.size / 2.0 68 | pos.x -= screen_size / 2.0 69 | pos.y -= label.character_size / 2 + 1 70 | when Label::Orientation::Right 71 | pos.x = label.size.x - screen_size 72 | end 73 | pos 74 | end 75 | end 76 | end 77 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/layout.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class LayoutDesign < ContainerDesign 10 | def render(widget, transform, graphics) 11 | pos = Vector3f.new widget.position.x, widget.position.y, 0.0 12 | transform = Matrix.translate transform, pos.to_f32 13 | render_children widget, transform, graphics 14 | end 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/text_box.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class TextBoxDesign < DesignDrawer 10 | def initialize(@font : Font) 11 | @cache = DrawableCache(Text).new do |widget| 12 | Text.new @font 13 | end 14 | end 15 | 16 | def render(widget, transform, graphics) 17 | box = widget.as(TextBox) 18 | text = @cache.find box 19 | update_text text, box 20 | draw_text text, transform, graphics 21 | end 22 | 23 | def update_text(text, box) 24 | string = insert_cursor box 25 | string, screen_size = crop_text string, box 26 | text.text = string 27 | text.size = box.character_size 28 | text.position = box.position 29 | end 30 | 31 | def draw_text(text, transform, graphics) 32 | graphics.draw text, transform 33 | end 34 | 35 | def insert_cursor(box) 36 | string = box.text.dup 37 | if box.use_cursor? 38 | string = string.insert box.cursor_position, '|' 39 | end 40 | string 41 | end 42 | 43 | def crop_text(string, box) 44 | size = 0.0 45 | rows = 1 46 | wrapped = "" 47 | character_size = box.character_size 48 | linespacing = @font.get_linespacing character_size 49 | string.each_char_with_index do |char, index| 50 | glyph = @font.get_glyph char, character_size 51 | size += glyph.advance 52 | if index + 1 < string.size 53 | next_glyph = @font.get_glyph string[index+1], character_size 54 | next_advance = next_glyph.advance 55 | else 56 | next_advance = 0 57 | end 58 | wrapped += char 59 | if size + next_advance >= box.size.x 60 | char = '\n' 61 | wrapped += char 62 | end 63 | if char == '\n' 64 | size = 0.0 65 | rows += 1 66 | end 67 | if !box.use_cursor? && rows * linespacing > box.size.y 68 | wrapped += "..." 69 | break 70 | end 71 | end 72 | return wrapped, size 73 | end 74 | end 75 | end 76 | end -------------------------------------------------------------------------------- /src/boleite/gui/default_design/window.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | end 4 | 5 | abstract class Design 6 | end 7 | 8 | class DefaultDesign < Design 9 | class WindowDesign < ContainerDesign 10 | struct WindowDrawables 11 | property body, body_border, header, header_border 12 | 13 | @body = Shape.new 14 | @body_border = Shape.new 15 | @header = Shape.new 16 | @header_border = Shape.new 17 | end 18 | 19 | @cache = DrawableCache(WindowDrawables).new do |widget| 20 | WindowDrawables.new 21 | end 22 | 23 | def render(widget, transform, graphics) 24 | window = widget.as(Window) 25 | drawables = @cache.find widget 26 | update_drawables drawables, window 27 | draw_drawables drawables, transform, graphics 28 | 29 | header_transform = Matrix.mul transform, drawables.header.transformation 30 | graphics.draw window.header_label, header_transform 31 | 32 | transform = Matrix.mul transform, drawables.body.transformation 33 | if button = window.close_button 34 | graphics.draw button, transform 35 | end 36 | render_children widget, transform, graphics 37 | end 38 | 39 | def update_drawables(drawables, window) 40 | update_drawables_header drawables, window 41 | update_drawables_body drawables, window 42 | end 43 | 44 | def draw_drawables(drawables, transform, graphics) 45 | graphics.draw drawables.body_border, transform 46 | graphics.draw drawables.body, transform 47 | graphics.draw drawables.header_border, transform 48 | graphics.draw drawables.header, transform 49 | end 50 | 51 | def update_drawables_header(drawables, window) 52 | offset = Vector2f.zero 53 | header_size = Vector2f.new window.size.x, window.header_size 54 | pos = window.position 55 | update_border drawables.header_border, pos, header_size, BORDER_SIZE, offset 56 | update_body drawables.header, pos, header_size, PRIMARY_COLOR, offset 57 | end 58 | 59 | def update_drawables_body(drawables, window) 60 | offset = Vector2f.zero 61 | pos = window.position 62 | size = window.size 63 | update_border drawables.body_border, pos, size, BORDER_SIZE, offset 64 | update_body drawables.body, pos, size, SECONDARY_COLOR, offset 65 | end 66 | 67 | def update_body(shape, pos, size, color, offset) 68 | shape.position = pos + offset 69 | shape.color = color 70 | update_vertices shape, size.to_f32 71 | end 72 | 73 | def update_border(shape, pos, size, border, offset) 74 | shape.position = pos - border + offset 75 | shape.color = BORDER_COLOR 76 | shape_size = (size + border * 2.0).to_f32 77 | update_vertices shape, shape_size 78 | end 79 | 80 | def should_update_vertices?(shape, size) 81 | shape.num_vertices < 6 || shape[3].x != size.x || shape[3].y != size.y 82 | end 83 | 84 | def update_vertices(shape, size) 85 | if should_update_vertices? shape, size 86 | shape.clear_vertices 87 | shape.add_vertex 0, 0 88 | shape.add_vertex 0, size.y 89 | shape.add_vertex size.x, 0 90 | shape.add_vertex size 91 | shape.add_vertex size.x, 0 92 | shape.add_vertex 0, size.y 93 | end 94 | end 95 | end 96 | end 97 | end -------------------------------------------------------------------------------- /src/boleite/gui/design.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class DesignDrawer 3 | abstract def render(widget, transform, graphics) 4 | end 5 | 6 | abstract class Design 7 | def get_drawer(widget) 8 | raise Exception.new "Unkown widget type given '#{widget}' for design." 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /src/boleite/gui/desktop.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Root < Container 3 | end 4 | 5 | class Desktop < Root 6 | def initialize 7 | super 8 | end 9 | 10 | def reset_acc_allocation 11 | @acc_allocation = @allocation 12 | end 13 | 14 | def update_acc_allocation 15 | @acc_allocation = @acc_allocation.merge @allocation 16 | @acc_allocation = @acc_allocation.expand 2.0 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/boleite/gui/graphics.cr: -------------------------------------------------------------------------------- 1 | require "./default_design/*" 2 | 3 | class Boleite::GUI 4 | class Graphics 5 | @gfx : GraphicsContext 6 | @renderer : Renderer 7 | @camera : Camera 8 | @design : Design 9 | 10 | property design 11 | 12 | def initialize(@gfx, default_font) 13 | target = @gfx.main_target 14 | @camera = Camera2D.new target.width.to_f32, target.height.to_f32, 0f32, 1f32 15 | @renderer = Renderer.new @gfx, @camera 16 | @design = DefaultDesign.new @gfx, default_font 17 | end 18 | 19 | def clear(rect : FloatRect) 20 | @renderer.clear rect.to_i, Color.transparent 21 | end 22 | 23 | def draw(drawable : Drawable, transform = Matrix44f32.identity) 24 | @renderer.draw drawable, transform 25 | end 26 | 27 | def draw(widget, transform = Matrix44f32.identity) 28 | drawer = @design.get_drawer widget 29 | drawer.render(widget, transform, self) 30 | end 31 | 32 | def render 33 | @renderer.present 34 | end 35 | 36 | def target_size 37 | @gfx.main_target.size 38 | end 39 | end 40 | end -------------------------------------------------------------------------------- /src/boleite/gui/grid_layout.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class GridLayout < Container 3 | include CrystalClear 4 | 5 | @padding = Vector2f.new(1.0, 1.0) 6 | @handling_state_change = false 7 | 8 | getter padding 9 | setter_state padding 10 | 11 | def initialize(@cells : Vector2i) 12 | super() 13 | end 14 | 15 | def max_children : Int32 16 | @cells.x * @cells.y 17 | end 18 | 19 | protected def on_state_change 20 | return if @handling_state_change 21 | @handling_state_change = true 22 | arrange_child_widgets 23 | @handling_state_change = false 24 | super 25 | end 26 | 27 | protected def arrange_child_widgets 28 | pos = @padding 29 | count = 0 30 | largest_y = 0.0 31 | each_widget do |child| 32 | child.position = pos 33 | pos.x += child.size.x + @padding.x 34 | largest_y = child.size.y if child.size.y > largest_y 35 | count += 1 36 | if count % @cells.x == 0 37 | pos.y += largest_y + @padding.y 38 | pos.x = @padding.x 39 | largest_y = 0.0 40 | end 41 | end 42 | end 43 | 44 | invariant @children.size <= max_children 45 | end 46 | end -------------------------------------------------------------------------------- /src/boleite/gui/gui.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | include CrystalClear 3 | 4 | @graphics : Graphics 5 | @router = InputRouter.new 6 | @receiver = InputReceiver.new 7 | @roots = [] of Root 8 | 9 | delegate target_size, to: @graphics 10 | 11 | def initialize(gfx, default_font) 12 | @graphics = Graphics.new gfx, default_font 13 | 14 | @router.register @receiver 15 | @receiver.register_instance RootMouseOver.new(self), ->handle_root_mouse_over(Vector2f) 16 | end 17 | 18 | def enable(parent_input) 19 | parent_input.register @router 20 | end 21 | 22 | def disable(parent_input) 23 | parent_input.unregister @router 24 | end 25 | 26 | def global_input_router 27 | @router 28 | end 29 | 30 | def global_input_receiver 31 | @receiver 32 | end 33 | 34 | requires !@roots.includes? root 35 | def add_root(root : Root) 36 | @router.register root.input 37 | @roots << root 38 | root.mark_dirty 39 | end 40 | 41 | def remove_root(root : Root) 42 | result = @roots.delete root 43 | if result 44 | @router.unregister result.input 45 | allocation = root.acc_allocation 46 | allocation = allocation.expand 2.0 47 | @graphics.clear allocation 48 | each_root do |r| 49 | r.mark_dirty if allocation.intersects? r.acc_allocation 50 | end 51 | end 52 | end 53 | 54 | requires @roots.includes? root 55 | def move_to_front(root : Root) 56 | if @roots.last != root 57 | @roots.delete root 58 | @router.unregister root.input 59 | @roots << root 60 | @router.register_at 1, root.input 61 | root.mark_dirty 62 | end 63 | end 64 | 65 | def render 66 | repaint_widgets = [] of Root 67 | repaint_flags = find_repaint_widgets 68 | repaint_flags.each_with_index do |flag, index| 69 | if flag 70 | widget = @roots[index] 71 | allocation = widget.acc_allocation 72 | widget.reset_acc_allocation 73 | @graphics.clear allocation 74 | repaint_widgets << widget 75 | end 76 | end 77 | repaint_widgets.each do |widget| 78 | @graphics.draw widget if widget.visible? 79 | widget.clear_repaint 80 | end 81 | @graphics.render 82 | end 83 | 84 | def find_repaint_widgets 85 | repaint_matrix = @roots.map &.repaint? 86 | retry = true 87 | while retry 88 | retry = false 89 | repaint_matrix.each_with_index do |flag, index| 90 | if flag 91 | widget = @roots[index] 92 | allocation = widget.acc_allocation 93 | @roots.each_with_index do |other, other_index| 94 | if !repaint_matrix[other_index] && other.acc_allocation.intersects? allocation 95 | repaint_matrix[other_index] = true 96 | retry = true 97 | end 98 | end 99 | end 100 | end 101 | end 102 | repaint_matrix 103 | end 104 | 105 | def each_root 106 | @roots.each { |root| yield root } 107 | end 108 | 109 | def pulse 110 | each_root &.pulse.emit 111 | end 112 | 113 | private def handle_root_mouse_over(pos : Vector2f) 114 | @roots.reverse.each do |root| 115 | next unless root.visible? 116 | 117 | to_front = false 118 | to_front = true if root.absolute_allocation.contains? pos 119 | if to_front 120 | move_to_front root 121 | break 122 | end 123 | end 124 | end 125 | end -------------------------------------------------------------------------------- /src/boleite/gui/helpers.cr: -------------------------------------------------------------------------------- 1 | # This file needs to be read first! 2 | class Boleite::GUI 3 | abstract class Widget 4 | macro setter_state(*args) 5 | {% for name in args %} 6 | def {{name}}=(@{{name}}) 7 | state_change.emit 8 | end 9 | {% end %} 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /src/boleite/gui/image.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class Image < Widget 3 | @texture : Texture? 4 | 5 | getter texture 6 | setter_state texture 7 | 8 | def initialize() 9 | super 10 | end 11 | 12 | def initialize(@texture) 13 | super() 14 | if texture = @texture 15 | self.size = texture.size.to_f 16 | else 17 | self.size = Vector2f.zero 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /src/boleite/gui/input/container.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class ContainerInputPass 3 | def initialize(@widget : Container) 4 | end 5 | 6 | def interested?(event : InputEvent) : Bool 7 | true 8 | end 9 | 10 | def translate(event : InputEvent) 11 | {event} 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /src/boleite/gui/input/gui.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class RootMouseOver 3 | @pos = Vector2f.zero 4 | @clicked_inside = false 5 | 6 | def initialize(@gui : GUI) 7 | end 8 | 9 | def interested?(event : InputEvent) : Bool 10 | if event.is_a? MousePosEvent 11 | @pos = event.pos 12 | elsif event.is_a? MouseButtonEvent 13 | @gui.each_root do |root| 14 | if root.absolute_allocation.contains? @pos 15 | return event.action == InputAction::Release 16 | end 17 | end 18 | end 19 | return false 20 | end 21 | 22 | def translate(event : InputEvent) 23 | {@pos} 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /src/boleite/gui/input/handler.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class InputHandler < InputReceiver 3 | @widget = WeakRef(Widget | Nil).new nil 4 | 5 | def widget=(widget) 6 | @widget = WeakRef(Widget).new widget 7 | end 8 | 9 | def widget 10 | if widget = @widget.try(&.value) 11 | widget 12 | else 13 | nil 14 | end 15 | end 16 | 17 | def process(event : InputEvent) 18 | if widget = self.widget 19 | super(event) if widget.visible? && widget.enabled? 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /src/boleite/gui/input/input_field.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class InputField < Widget 3 | end 4 | 5 | class InputFieldEnterText 6 | def initialize(@widget : InputField) 7 | end 8 | 9 | def interested?(event : InputEvent) : Bool 10 | @widget.input_focus? && event.is_a? CharEvent 11 | end 12 | 13 | def translate(event : InputEvent) 14 | event = event.as(CharEvent) 15 | event.claim 16 | {event.char.chr} 17 | end 18 | end 19 | 20 | class InputFieldKeyPress 21 | def initialize(@widget : InputField, @key : Key) 22 | end 23 | 24 | def interested?(event : InputEvent) : Bool 25 | if @widget.input_focus? && event.is_a? KeyEvent 26 | event = event.as(KeyEvent) 27 | event.key == @key && event.action != InputAction::Release 28 | else 29 | false 30 | end 31 | end 32 | 33 | def translate(event : InputEvent) 34 | event.claim 35 | Tuple.new 36 | end 37 | end 38 | 39 | class InputFieldCatchAllKey 40 | def initialize(@widget : InputField) 41 | end 42 | 43 | def interested?(event : InputEvent) : Bool 44 | @widget.input_focus? && event.is_a? KeyEvent 45 | end 46 | 47 | def translate(event : InputEvent) 48 | event = event.as(KeyEvent) 49 | event.claim 50 | {event.key, event.action} 51 | end 52 | end 53 | 54 | class InputFieldLoseFocus 55 | def initialize(@widget : InputField) 56 | end 57 | 58 | def interested?(event : InputEvent) : Bool 59 | !@widget.has_mouse_focus? && event.is_a? MouseButtonEvent 60 | end 61 | 62 | def translate(event : InputEvent) 63 | Tuple.new 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /src/boleite/gui/input/widget.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class WidgetMouseEnter 3 | def initialize(@widget : Widget) 4 | end 5 | 6 | def interested?(event : InputEvent) : Bool 7 | if @widget.has_mouse_focus? == false && event.is_a? MousePosEvent 8 | allocation = @widget.absolute_allocation 9 | allocation.contains? event.pos 10 | else 11 | false 12 | end 13 | end 14 | 15 | def translate(event : InputEvent) 16 | Tuple.new 17 | end 18 | end 19 | 20 | class WidgetMouseLeave 21 | def initialize(@widget : Widget) 22 | end 23 | 24 | def interested?(event : InputEvent) : Bool 25 | if @widget.has_mouse_focus? && event.is_a? MousePosEvent 26 | allocation = @widget.absolute_allocation 27 | !allocation.contains? event.pos 28 | else 29 | false 30 | end 31 | end 32 | 33 | def translate(event : InputEvent) 34 | Tuple.new 35 | end 36 | end 37 | 38 | class WidgetMouseOver 39 | def initialize(@widget : Widget) 40 | end 41 | 42 | def interested?(event : InputEvent) : Bool 43 | if @widget.has_mouse_focus? && event.is_a? MousePosEvent 44 | true 45 | else 46 | false 47 | end 48 | end 49 | 50 | def translate(event : InputEvent) 51 | event = event.as(MousePosEvent) 52 | pos = @widget.absolute_position 53 | {event.pos - pos} 54 | end 55 | end 56 | 57 | class WidgetMouseClick 58 | @last_pos = Vector2f.zero 59 | @state = false 60 | 61 | def initialize(@widget : Widget, @button : Mouse) 62 | end 63 | 64 | def interested?(event : InputEvent) : Bool 65 | if @widget.has_mouse_focus? 66 | if event.is_a? MousePosEvent 67 | @last_pos = event.pos 68 | elsif event.is_a? MouseButtonEvent 69 | return handle_button_state(event) 70 | end 71 | else 72 | @state = false 73 | end 74 | return false 75 | end 76 | 77 | def translate(event : InputEvent) 78 | pos = @widget.absolute_position 79 | {@last_pos - pos} 80 | end 81 | 82 | def handle_button_state(event : MouseButtonEvent) : Bool 83 | old_state = @state 84 | if event.button == @button 85 | @state = event.action != InputAction::Release 86 | end 87 | old_state == true && old_state != @state 88 | end 89 | end 90 | 91 | class WidgetBasicClick 92 | @pos = Vector2f.zero 93 | @clicked_inside = false 94 | 95 | def initialize(@widget : Widget, @button : Mouse) 96 | end 97 | 98 | def interested?(event : InputEvent) : Bool 99 | if event.is_a? MousePosEvent 100 | @pos = event.pos 101 | elsif event.is_a? MouseButtonEvent 102 | if @widget.absolute_allocation.contains? @pos 103 | if event.button == @button && event.action == InputAction::Press 104 | @clicked_inside = true 105 | end 106 | else 107 | @clicked_inside = false 108 | end 109 | 110 | if @clicked_inside 111 | event.claim 112 | end 113 | 114 | return @clicked_inside && event.action == InputAction::Release 115 | end 116 | return false 117 | end 118 | 119 | def translate(event : InputEvent) 120 | event.claim 121 | pos = @widget.absolute_position 122 | {@pos - pos} 123 | end 124 | end 125 | end -------------------------------------------------------------------------------- /src/boleite/gui/input/window.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class WindowHeaderDrag 3 | @dragging = false 4 | @pos = Vector2f.zero 5 | @last = Vector2f.zero 6 | 7 | def initialize(@widget : Window) 8 | end 9 | 10 | def interested?(event : InputEvent) : Bool 11 | if event.is_a? MousePosEvent 12 | @last = @pos 13 | @pos = event.pos 14 | elsif event.is_a? MouseButtonEvent 15 | @dragging = event.button == Mouse::Left 16 | @dragging = @dragging && event.action != InputAction::Release 17 | @dragging = @dragging && @widget.header_allocation.contains? @pos 18 | else 19 | @dragging = false 20 | end 21 | @dragging 22 | end 23 | 24 | def translate(event : InputEvent) 25 | event.claim 26 | {@pos - @last} 27 | end 28 | end 29 | 30 | class WindowClaimLeftovers 31 | def initialize(@widget : Window) 32 | end 33 | 34 | def interested?(event : InputEvent) : Bool 35 | @widget.has_mouse_focus? 36 | end 37 | 38 | def translate(event : InputEvent) 39 | event.claim 40 | Tuple.new 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /src/boleite/gui/input_field.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Widget 3 | end 4 | 5 | class InputField < Widget 6 | @label = Label.new 7 | @input_focus = false 8 | 9 | getter label 10 | getter? input_focus 11 | 12 | Cute.signal click(pos : Vector2f) 13 | Cute.signal text_entered(char : Char) 14 | Cute.signal key_pressed(key : Key, action : InputAction) 15 | Cute.signal lose_input_focus 16 | 17 | def initialize 18 | super 19 | 20 | @label.orientation = Label::Orientation::Left 21 | @label.parent = self 22 | 23 | state_change.on &->update_label_size 24 | text_entered.on &->handle_text_input(Char) 25 | click.on &->(pos : Vector2f) { self.input_focus = true } 26 | lose_input_focus.on &-> { self.input_focus = false } 27 | 28 | clicker = WidgetBasicClick.new self, Mouse::Left 29 | @input.register_instance clicker, click 30 | @input.register_instance InputFieldEnterText.new(self), text_entered 31 | @input.register_instance InputFieldKeyPress.new(self, Key::Left), ->move_cursor_back 32 | @input.register_instance InputFieldKeyPress.new(self, Key::Right), ->move_cursor_forward 33 | @input.register_instance InputFieldKeyPress.new(self, Key::Backspace), ->handle_backspace 34 | @input.register_instance InputFieldCatchAllKey.new(self), key_pressed 35 | @input.register_persistent_instance InputFieldLoseFocus.new(self), lose_input_focus 36 | end 37 | 38 | def initialize(text, size = Vector2f.zero) 39 | self.initialize 40 | self.value = text 41 | self.size = size 42 | end 43 | 44 | def value=(text) 45 | @label.text = text 46 | @label.cursor_position = text.size 47 | state_change.emit 48 | end 49 | 50 | def value 51 | @label.text 52 | end 53 | 54 | def input_focus=(@input_focus) 55 | @label.use_cursor = @input_focus 56 | end 57 | 58 | protected def update_label_size 59 | @label.size = self.size 60 | end 61 | 62 | protected def handle_text_input(char : Char) 63 | pos = @label.cursor_position 64 | @label.text = @label.text.insert pos, char 65 | @label.cursor_position += 1 66 | state_change.emit 67 | end 68 | 69 | protected def move_cursor_back 70 | @label.cursor_position -= 1 71 | state_change.emit 72 | end 73 | 74 | protected def move_cursor_forward 75 | @label.cursor_position += 1 76 | state_change.emit 77 | end 78 | 79 | protected def handle_backspace 80 | cursor = @label.cursor_position 81 | text = @label.text 82 | if cursor > 0 83 | self.value = text[0, cursor-1] + text[cursor, text.size] 84 | @label.cursor_position -= 1 if cursor < text.size 85 | end 86 | state_change.emit 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /src/boleite/gui/label.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class Label < Widget 3 | enum Orientation 4 | Left 5 | Center 6 | Right 7 | end 8 | 9 | @text = "" 10 | @character_size = 12u32 11 | @orientation = Orientation::Left 12 | @use_cursor = false 13 | @cursor_position = 0 14 | @color = Color.white 15 | 16 | getter text, character_size, orientation, cursor_position, color 17 | getter? use_cursor 18 | setter_state text, character_size, orientation, use_cursor, cursor_position, color 19 | 20 | def initialize() 21 | super 22 | end 23 | 24 | def initialize(text, wanted_size) 25 | super() 26 | 27 | self.size = wanted_size 28 | self.text = text 29 | end 30 | 31 | def on_state_change 32 | @cursor_position = @text.size if @cursor_position > @text.size 33 | @cursor_position = 0 if @cursor_position < 0 34 | super 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /src/boleite/gui/layout.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class Layout < Container 3 | enum Style 4 | Vertical 5 | Horizontal 6 | end 7 | 8 | @padding = Vector2f.new(1.0, 1.0) 9 | @handling_state_change = false 10 | 11 | getter padding, style 12 | setter_state padding, style 13 | 14 | def initialize(@style : Style) 15 | super() 16 | end 17 | 18 | requires style == :vertical || style == :horizontal 19 | def initialize(style : Symbol) 20 | @style = case style 21 | when :vertical then Style::Vertical 22 | when :horizontal then Style::Horizontal 23 | else Style::Vertical 24 | end 25 | super() 26 | end 27 | 28 | protected def on_state_change 29 | return if @handling_state_change 30 | @handling_state_change = true 31 | arrange_child_widgets 32 | @handling_state_change = false 33 | super 34 | end 35 | 36 | protected def arrange_child_widgets 37 | pos = @padding 38 | each_widget do |child| 39 | child.position = pos 40 | case @style 41 | when Style::Vertical then pos.y += child.size.y + @padding.y 42 | when Style::Horizontal then pos.x += child.size.x + @padding.x 43 | end 44 | end 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /src/boleite/gui/paste.shader: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | depth 4 | { 5 | enabled = false; 6 | function = Always; 7 | } 8 | 9 | blend 10 | { 11 | enabled = true; 12 | function = Add; 13 | sourceFactor = SourceAlpha; 14 | destinationFactor = OneMinusSourceAlpha; 15 | } 16 | 17 | vertex 18 | { 19 | layout(location = 0) in vec2 position; 20 | layout(location = 1) in vec2 uv; 21 | out VertexData { 22 | vec2 uv; 23 | } outputVertex; 24 | void main() 25 | { 26 | vec4 pos = vec4(position, 0, 1); 27 | gl_Position = pos; 28 | outputVertex.uv = uv; 29 | } 30 | } 31 | 32 | fragment 33 | { 34 | layout(location = 0) out vec4 outputColor; 35 | uniform sampler2D colorTexture; 36 | in VertexData { 37 | vec2 uv; 38 | } inputVertex; 39 | void main() 40 | { 41 | vec4 color = texture(colorTexture, inputVertex.uv); 42 | outputColor = color; 43 | } 44 | } -------------------------------------------------------------------------------- /src/boleite/gui/renderer.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Renderer 2 | end 3 | 4 | class Boleite::GUI 5 | class Renderer < Boleite::Renderer 6 | include CrystalClear 7 | 8 | struct Vertex < Vertex 9 | @pos = Vector2f32.zero 10 | @uv = Vector2f32.zero 11 | 12 | def initialize(x, y, u, v) 13 | @pos = Vector2f32.new(x, y) 14 | @uv = Vector2f32.new(u, v) 15 | end 16 | end 17 | 18 | @framebuffer : FrameBuffer 19 | @texture : Texture 20 | @vertices : VertexBufferObject 21 | @paste_shader : Shader 22 | 23 | def initialize(@gfx : GraphicsContext, @camera : Camera) 24 | target = @gfx.main_target 25 | @framebuffer = @gfx.create_frame_buffer 26 | @texture = @gfx.create_texture 27 | @texture.create target.width, target.height, Texture::Format::RGBA, Texture::Type::Integer8 28 | @framebuffer.attach_buffer @texture, :color, 0u8 29 | @vertices = @gfx.create_vertex_buffer_object 30 | @paste_shader = create_paste_shader @gfx 31 | create_vertices 32 | end 33 | 34 | def create_paste_shader(gfx) : Shader 35 | source = {{`cat #{__DIR__}/paste.shader`.stringify }} 36 | parser = ShaderParser.new 37 | parser.parse source 38 | gfx.create_shader(parser) 39 | end 40 | 41 | def create_vertices 42 | layout = Boleite::VertexLayout.new [ 43 | Boleite::VertexAttribute.new(0, 2, :float, 16u32, 0u32, 0u32), 44 | Boleite::VertexAttribute.new(0, 2, :float, 16u32, 8u32, 0u32) 45 | ] 46 | @vertices.layout = layout 47 | @vertices.primitive = Boleite::Primitive::TrianglesStrip 48 | buffer = @vertices.create_buffer 49 | buffer.add_data Vertex.new(-1f32, -1f32, 0f32, 0f32) 50 | buffer.add_data Vertex.new( 1f32, -1f32, 1f32, 0f32) 51 | buffer.add_data Vertex.new(-1f32, 1f32, 0f32, 1f32) 52 | buffer.add_data Vertex.new( 1f32, 1f32, 1f32, 1f32) 53 | end 54 | 55 | def clear(color : Colorf) : Void 56 | size = @texture.size.to_i 57 | clear IntRect.new(0, 0, size.x, size.y), color 58 | end 59 | 60 | def clear(rect : IntRect, color : Colorf) : Void 61 | top = @texture.size.y.to_i - rect.top 62 | bottom = top - rect.height 63 | rect.top = bottom 64 | @framebuffer.activate do 65 | @gfx.scissor = rect 66 | @gfx.clear color 67 | @gfx.scissor = nil 68 | end 69 | end 70 | 71 | requires drawcall.shader 72 | def draw(drawcall : DrawCallContext) : Void 73 | @framebuffer.activate do 74 | if shader = drawcall.shader 75 | apply_shader_settings shader, drawcall.transformation, drawcall.uniforms 76 | drawcall.buffers.each { |buffer| drawcall.vertices.attach_buffer(buffer, true) } 77 | shader.activate do 78 | drawcall.vertices.render(1) 79 | end 80 | end 81 | end 82 | end 83 | 84 | def present : Void 85 | @paste_shader.set_parameter "colorTexture", @texture 86 | @paste_shader.activate do 87 | @vertices.render(1) 88 | end 89 | end 90 | end 91 | end -------------------------------------------------------------------------------- /src/boleite/gui/root.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | abstract class Root < Container 3 | end 4 | end -------------------------------------------------------------------------------- /src/boleite/gui/text_box.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class TextBox < Widget 3 | @text = "" 4 | @character_size = 20u32 5 | @use_cursor = false 6 | @cursor_position = 0 7 | 8 | getter text, character_size, cursor_position 9 | getter? use_cursor 10 | setter_state text, character_size, use_cursor, cursor_position 11 | 12 | def initialize() 13 | super 14 | end 15 | 16 | def initialize(text, wanted_size) 17 | super() 18 | 19 | self.size = wanted_size 20 | self.text = text 21 | end 22 | 23 | def on_state_change 24 | @cursor_position = @text.size if @cursor_position > @text.size 25 | @cursor_position = 0 if @cursor_position < 0 26 | super 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /src/boleite/gui/widget.cr: -------------------------------------------------------------------------------- 1 | require "weak_ref" 2 | 3 | class Boleite::GUI 4 | abstract class Widget 5 | include CrystalClear 6 | 7 | getter name, allocation, input 8 | getter? repaint 9 | setter_state name, visible, enabled 10 | 11 | @name = "" 12 | @input = InputHandler.new 13 | @parent = WeakRef(Widget | Nil).new nil 14 | @allocation = FloatRect.new 15 | @visible = true 16 | @enabled = true 17 | @repaint = true 18 | @mouse_focus = false 19 | @in_state_change = false 20 | 21 | Cute.signal mouse_enter 22 | Cute.signal mouse_leave 23 | Cute.signal mouse_over(pos : Vector2f) 24 | Cute.signal left_click(pos : Vector2f) 25 | Cute.signal right_click(pos : Vector2f) 26 | Cute.signal state_change 27 | Cute.signal pulse 28 | 29 | def initialize 30 | state_change.on &->on_state_change 31 | mouse_enter.on &->on_mouse_enter 32 | mouse_leave.on &->on_mouse_leave 33 | 34 | @input.widget = self 35 | @input.register_instance WidgetMouseEnter.new(self), mouse_enter 36 | @input.register_instance WidgetMouseLeave.new(self), mouse_leave 37 | @input.register_instance WidgetMouseOver.new(self), mouse_over 38 | @input.register_instance WidgetMouseClick.new(self, Mouse::Left), left_click 39 | @input.register_instance WidgetMouseClick.new(self, Mouse::Right), right_click 40 | end 41 | 42 | def visible? 43 | visible = @visible 44 | if visible && (parent = self.parent) 45 | visible &= parent.visible? 46 | end 47 | visible 48 | end 49 | 50 | def enabled? 51 | enabled = @enabled 52 | if enabled && (parent = self.parent) 53 | enabled &= parent.enabled? 54 | end 55 | enabled 56 | end 57 | 58 | def clear_repaint 59 | @repaint = false 60 | end 61 | 62 | def mark_dirty 63 | @repaint = true 64 | end 65 | 66 | def has_mouse_focus? 67 | @mouse_focus 68 | end 69 | 70 | def position 71 | Vector2f.new @allocation.left, @allocation.top 72 | end 73 | 74 | def position=(pos) 75 | @allocation.left = pos.x 76 | @allocation.top = pos.y 77 | state_change.emit 78 | end 79 | 80 | def move(pos) 81 | self.position = self.position + pos 82 | end 83 | 84 | def absolute_position 85 | pos = position 86 | if parent = self.parent 87 | pos = parent.absolute_position + pos 88 | end 89 | pos 90 | end 91 | 92 | def absolute_allocation 93 | pos = absolute_position 94 | allocation = @allocation.dup 95 | allocation.left = pos.x 96 | allocation.top = pos.y 97 | allocation 98 | end 99 | 100 | def size 101 | Vector2f.new @allocation.width, @allocation.height 102 | end 103 | 104 | def size=(size) 105 | @allocation.width = size.x 106 | @allocation.height = size.y 107 | state_change.emit 108 | end 109 | 110 | requires self.parent.nil? || parent == nil 111 | requires parent != self 112 | def parent=(parent) 113 | @parent = WeakRef(Widget).new parent 114 | state_change.emit 115 | end 116 | 117 | def parent=(val : Nil) 118 | @parent = nil 119 | end 120 | 121 | def parent 122 | if parent = @parent.try(&.value) 123 | parent 124 | else 125 | nil 126 | end 127 | end 128 | 129 | protected def on_state_change 130 | @repaint = true 131 | return if @in_state_change 132 | @in_state_change = true 133 | if parent = self.parent 134 | parent.state_change.emit 135 | end 136 | @in_state_change = false 137 | end 138 | 139 | protected def on_mouse_enter 140 | @mouse_focus = true 141 | end 142 | 143 | protected def on_mouse_leave 144 | @mouse_focus = false 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /src/boleite/gui/window.cr: -------------------------------------------------------------------------------- 1 | class Boleite::GUI 2 | class Window < Root 3 | 4 | DEFAULT_SIZE = Vector2f.new 100.0, 30.0 5 | DEFAULT_HEADER_SIZE = 24.0 6 | 7 | @header_size = DEFAULT_HEADER_SIZE 8 | @header_label = Label.new 9 | @close_button : Button? 10 | 11 | getter header_size, header_label, close_button 12 | setter_state header_size 13 | 14 | Cute.signal header_drag(pos : Vector2f) 15 | 16 | def initialize 17 | super 18 | self.min_size = DEFAULT_SIZE 19 | 20 | @header_label.position = Vector2f.zero 21 | @header_label.parent = self 22 | 23 | state_change.on &->update_header_size 24 | header_drag.on &->move(Vector2f) 25 | @input.register_instance WindowHeaderDrag.new(self), header_drag 26 | @input.register_instance WindowClaimLeftovers.new(self), ->{} 27 | end 28 | 29 | def header_allocation 30 | pos = absolute_position 31 | FloatRect.new pos.x, pos.y, size.x, @header_size 32 | end 33 | 34 | def header_text=(text) 35 | @header_label.text = text 36 | end 37 | 38 | def header_character_size=(size) 39 | @header_label.character_size = size 40 | end 41 | 42 | def reset_acc_allocation 43 | @acc_allocation = @allocation 44 | end 45 | 46 | def update_acc_allocation 47 | @acc_allocation = @acc_allocation.merge @allocation 48 | @acc_allocation = @acc_allocation.expand 2.0 49 | end 50 | 51 | def set_next_to(other : Window) 52 | other_pos = other.absolute_position 53 | other_size = other.size 54 | 55 | if p = parent 56 | other_pos -= p.absolute_position 57 | end 58 | 59 | self.position = other_pos + other_size * 0.5 60 | end 61 | 62 | def add_close_button(&block) 63 | button = Button.new "X", Vector2f.new(20.0, @header_size) 64 | button.click.on { |pos| block.call } 65 | button.parent = self 66 | @close_button = button 67 | state_change.emit 68 | end 69 | 70 | def remove_close_button 71 | @close_button = nil 72 | state_change.emit 73 | end 74 | 75 | def add(child) 76 | child.position += Boleite::Vector2f.new 0.0, @header_size 77 | super child 78 | end 79 | 80 | protected def update_header_size 81 | @header_label.size = Vector2f.new size.x, @header_size 82 | if button = @close_button 83 | button.position = Vector2f.new size.x - 20.0, 0.0 84 | button.size = Vector2f.new 20.0, @header_size 85 | end 86 | end 87 | 88 | protected def pass_input_to_children(event : InputEvent) 89 | if button = @close_button 90 | button.input.process event 91 | end 92 | super event unless event.claimed? 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /src/boleite/input/enums.cr: -------------------------------------------------------------------------------- 1 | enum Boleite::Key 2 | Unknown 3 | Space 4 | Apostrophe 5 | Comma 6 | Minus 7 | Period 8 | Slash 9 | Num0 10 | Num1 11 | Num2 12 | Num3 13 | Num4 14 | Num5 15 | Num6 16 | Num7 17 | Num8 18 | Num9 19 | Semicolon 20 | Equal 21 | A 22 | B 23 | C 24 | D 25 | E 26 | F 27 | G 28 | H 29 | I 30 | J 31 | K 32 | L 33 | M 34 | N 35 | O 36 | P 37 | Q 38 | R 39 | S 40 | T 41 | U 42 | V 43 | W 44 | X 45 | Y 46 | Z 47 | LeftBracket 48 | Backslash 49 | RightBracket 50 | GraveAccent 51 | World1 52 | World2 53 | 54 | Escape 55 | Enter 56 | Tab 57 | Backspace 58 | Insert 59 | Delete 60 | Right 61 | Left 62 | Down 63 | Up 64 | PageUp 65 | PageDown 66 | Home 67 | End 68 | CapsLock 69 | ScrollLock 70 | NumLock 71 | PrintScreen 72 | Pause 73 | F1 74 | F2 75 | F3 76 | F4 77 | F5 78 | F6 79 | F7 80 | F8 81 | F9 82 | F10 83 | F11 84 | F12 85 | F13 86 | F14 87 | F15 88 | F16 89 | F17 90 | F18 91 | F19 92 | F20 93 | F21 94 | F22 95 | F23 96 | F24 97 | F25 98 | KeyPad0 99 | KeyPad1 100 | KeyPad2 101 | KeyPad3 102 | KeyPad4 103 | KeyPad5 104 | KeyPad6 105 | KeyPad7 106 | KeyPad8 107 | KeyPad9 108 | KeyPadDecimal 109 | KeyPadDivide 110 | KeyPadMultiply 111 | KeyPadSubtract 112 | KeyPadAdd 113 | KeyPadEnter 114 | KeyPadEqual 115 | LeftShift 116 | LeftControl 117 | LeftAlt 118 | LeftSuper 119 | RightShift 120 | RightControl 121 | RightAlt 122 | RightSuper 123 | Menu 124 | end 125 | 126 | enum Boleite::Mouse 127 | Unknown 128 | Button1 129 | Button2 130 | Button3 131 | Button4 132 | Button5 133 | Button6 134 | Button7 135 | Button8 136 | Last = Button8 137 | Left = Button1 138 | Right = Button2 139 | Middle = Button3 140 | end 141 | 142 | @[Flags] 143 | enum Boleite::KeyMod 144 | Shift 145 | Control 146 | Alt 147 | Super 148 | end 149 | 150 | enum Boleite::InputAction 151 | Unknown 152 | Press 153 | Release 154 | Repeat 155 | end 156 | -------------------------------------------------------------------------------- /src/boleite/input/input_action.cr: -------------------------------------------------------------------------------- 1 | class Boleite::ClosedAction 2 | def interested?(event : InputEvent) : Bool 3 | event.class == ClosedEvent 4 | end 5 | 6 | def translate(event : InputEvent) 7 | Tuple.new 8 | end 9 | end 10 | 11 | class Boleite::PassThroughAction 12 | def interested?(event : InputEvent) : Bool 13 | true 14 | end 15 | 16 | def translate(event : InputEvent) 17 | {event} 18 | end 19 | end -------------------------------------------------------------------------------- /src/boleite/input/input_event.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::InputEvent 2 | @claimed = false 3 | 4 | def claim 5 | @claimed = true 6 | end 7 | 8 | def claimed? 9 | @claimed 10 | end 11 | end 12 | 13 | class Boleite::ClosedEvent < Boleite::InputEvent 14 | end 15 | 16 | class Boleite::KeyEvent < Boleite::InputEvent 17 | getter key, action, mods 18 | 19 | def initialize(@key : Key, @action : InputAction, @mods : KeyMod) 20 | end 21 | end 22 | 23 | class Boleite::CharEvent < Boleite::InputEvent 24 | getter :char 25 | 26 | def initialize(@char : UInt32) 27 | end 28 | end 29 | 30 | class Boleite::MouseButtonEvent < Boleite::InputEvent 31 | getter :button, action, mods 32 | 33 | def initialize(@button : Mouse, @action : InputAction, @mods : KeyMod) 34 | end 35 | end 36 | 37 | class Boleite::MouseScrollEvent < Boleite::InputEvent 38 | getter :x_scroll, :y_scroll 39 | 40 | def scroll 41 | Vector2f.new(@x_scroll, @y_scroll) 42 | end 43 | 44 | def initialize(@x_scroll : Float64, @y_scroll : Float64) 45 | end 46 | end 47 | 48 | class Boleite::MousePosEvent < Boleite::InputEvent 49 | getter :x, :y 50 | 51 | def pos 52 | Vector2f.new(@x, @y) 53 | end 54 | 55 | def initialize(@x : Float64, @y : Float64) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /src/boleite/input/input_processor.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::InputProcessor 2 | abstract def process(event : Boleite::InputEvent) : Void 3 | end -------------------------------------------------------------------------------- /src/boleite/input/input_receiver.cr: -------------------------------------------------------------------------------- 1 | class Boleite::InputReceiver < Boleite::InputProcessor 2 | abstract class Glue 3 | abstract def interested?(input) : Bool 4 | abstract def execute(input) : Nil 5 | abstract def for?(type) : Bool 6 | end 7 | 8 | abstract class GlueImp(A, P) < Glue 9 | def initialize(@action : A, @callback : P) 10 | end 11 | 12 | def for?(type) : Bool 13 | A == type 14 | end 15 | 16 | def interested?(input) : Bool 17 | @action.interested? input 18 | end 19 | end 20 | 21 | class GlueProc(A, P) < GlueImp(A, P) 22 | def execute(input) : Nil 23 | args = @action.translate input 24 | @callback.call *args 25 | end 26 | end 27 | 28 | class GlueSignal(A, P) < GlueImp(A, P) 29 | def execute(input) : Nil 30 | args = @action.translate input 31 | @callback.emit *args 32 | end 33 | end 34 | 35 | @actions = [] of Glue 36 | @persistent_actions = [] of Glue 37 | 38 | def register(action_type, proc) 39 | register_instance action_type.new, proc 40 | end 41 | 42 | def register_persistent(action_type, proc) 43 | register_persistent_instance action_type.new, proc 44 | end 45 | 46 | def register_instance(action, signal : Cute::Signal) 47 | @actions << GlueSignal.new action, signal 48 | end 49 | 50 | def register_instance(action, proc) 51 | @actions << GlueProc.new action, proc 52 | end 53 | 54 | def register_persistent_instance(action, signal : Cute::Signal) 55 | @persistent_actions << GlueSignal.new action, signal 56 | end 57 | 58 | def register_persistent_instance(action, proc) 59 | @persistent_actions << GlueProc.new action, proc 60 | end 61 | 62 | def unregister(action_type) 63 | @actions.select! do |action| 64 | action.for? action_type 65 | end 66 | end 67 | 68 | def unregister_persistent(action_type) 69 | @persistent_actions.select! do |action| 70 | action.for? action_Type 71 | end 72 | end 73 | 74 | def clear 75 | @actions.clear 76 | @persistent_actions.clear 77 | end 78 | 79 | def process(event : Boleite::InputEvent) : Void 80 | process_list(@actions, event) unless event.claimed? 81 | process_list(@persistent_actions, event) 82 | end 83 | 84 | private def process_list(actions, event) 85 | actions.each{|act| act.execute(event) if act.interested?(event)} 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /src/boleite/input/input_router.cr: -------------------------------------------------------------------------------- 1 | class Boleite::InputRouter < Boleite::InputProcessor 2 | include CrystalClear 3 | 4 | @receivers = [] of InputProcessor 5 | 6 | def initialize() 7 | end 8 | 9 | requires @receivers.includes?(receiver) == false 10 | requires receiver != self 11 | def register(receiver) 12 | @receivers << receiver 13 | end 14 | 15 | requires @receivers.includes?(receiver) == false 16 | requires receiver != self 17 | def register_at(index, receiver) 18 | @receivers.insert index, receiver 19 | end 20 | 21 | def unregister(receiver) 22 | obj = @receivers.delete receiver 23 | end 24 | 25 | def process(event : Boleite::InputEvent) : Void 26 | @receivers.each do |receiver| 27 | receiver.process event 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /src/boleite/math/color.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Color 2 | def self.red 3 | Colorf.new 1f32, 0f32, 0f32, 1f32 4 | end 5 | 6 | def self.green 7 | Colorf.new 0f32, 1f32, 0f32, 1f32 8 | end 9 | 10 | def self.blue 11 | Colorf.new 0f32, 0f32, 1f32, 1f32 12 | end 13 | 14 | def self.black 15 | Colorf.new 0f32, 0f32, 0f32, 1f32 16 | end 17 | 18 | def self.white 19 | Colorf.new 1f32, 1f32, 1f32, 1f32 20 | end 21 | 22 | def self.yellow 23 | Colorf.new 1f32, 1f32, 0f32, 1f32 24 | end 25 | 26 | def self.pink 27 | Colorf.new 1f32, 0f32, 1f32, 1f32 28 | end 29 | 30 | def self.cyan 31 | Colorf.new 0f32, 1f32, 1f32, 1f32 32 | end 33 | 34 | def self.transparent 35 | Colorf.new 0f32, 0f32, 0f32, 0f32 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /src/boleite/math/matrix_imp.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::MatrixImp(Type, Dimension, Size) 2 | SIZE = Size 3 | DIMENSION = Dimension 4 | TYPE = Type 5 | 6 | private macro to_coord(index) 7 | Vector2u8.new({{index}}.to_u8 % Dimension, {{index}}.to_u8 // Dimension) 8 | end 9 | 10 | private macro to_index(x, y) 11 | {{y}} + Dimension * {{x}} 12 | end 13 | 14 | def self.identity : self 15 | self.new() 16 | end 17 | 18 | @elements : StaticArray(Type, Size) 19 | 20 | def initialize() 21 | @elements = StaticArray(Type, Size).new do |index| 22 | coord = to_coord(index) 23 | if coord.x == coord.y 24 | Type.new(1) 25 | else 26 | Type.new(0) 27 | end 28 | end 29 | end 30 | 31 | def initialize(@elements : StaticArray(Type, Size)) 32 | end 33 | 34 | def initialize(&block) 35 | @elements = StaticArray(Type, Size).new(Type.zero) 36 | @elements.map_with_index! do |value, index| 37 | yield index 38 | end 39 | end 40 | 41 | def initialize(*args) 42 | @elements = StaticArray(Type, Size).new(Type.zero) 43 | args.each_index do |index| 44 | @elements[index] = args[index] 45 | end 46 | end 47 | 48 | def map 49 | cpy = self.class.new do |index| 50 | yield @elements[index] 51 | end 52 | end 53 | 54 | def each 55 | @elements.each { |v| yield v } 56 | end 57 | 58 | def each_index 59 | @elements.each_index { |v| yield v } 60 | end 61 | 62 | def [](index) 63 | @elements[index] 64 | end 65 | 66 | def [](x, y) 67 | @elements[to_index(x, y)] 68 | end 69 | 70 | def []=(index, value) 71 | @elements[index] = value 72 | end 73 | 74 | def []=(x, y, value) 75 | @elements[to_index(x, y)] = value 76 | end 77 | 78 | def ==(other : self) 79 | @elements = other.elements 80 | end 81 | 82 | private macro def_conv_meth(name, type) 83 | {% if type == TYPE %} 84 | def {{name}} 85 | self 86 | end 87 | {% else %} 88 | def {{name}} 89 | MatrixImp({{type}}, Dimension, Size).new( 90 | StaticArray({{type}}, Size).new { |index| 91 | @elements[index].{{name}} 92 | } 93 | ) 94 | end 95 | {% end %} 96 | end 97 | 98 | def_conv_meth(to_i8, Int8) 99 | def_conv_meth(to_i16, Int16) 100 | def_conv_meth(to_i32, Int32) 101 | def_conv_meth(to_i64, Int64) 102 | 103 | def_conv_meth(to_u8, UInt8) 104 | def_conv_meth(to_u16, UInt16) 105 | def_conv_meth(to_u32, UInt32) 106 | def_conv_meth(to_u64, UInt64) 107 | 108 | def_conv_meth(to_f32, Float32) 109 | def_conv_meth(to_f64, Float64) 110 | 111 | def_conv_meth(to_i, Int32) 112 | def_conv_meth(to_u, UInt32) 113 | def_conv_meth(to_f, Float64) 114 | 115 | protected def elements 116 | @elements 117 | end 118 | end 119 | 120 | module Boleite 121 | alias Matrix33f32 = MatrixImp(Float32, 3, 9) 122 | alias Matrix33f64 = MatrixImp(Float64, 3, 9) 123 | alias Matrix44f32 = MatrixImp(Float32, 4, 16) 124 | alias Matrix44f64 = MatrixImp(Float64, 4, 16) 125 | 126 | alias Matrix33 = Matrix33f64 127 | alias Matrix44 = Matrix44f64 128 | end 129 | -------------------------------------------------------------------------------- /src/boleite/math/rect.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::Rect(Type) 2 | TYPE = Type 3 | 4 | @left : Type 5 | @top : Type 6 | @width : Type 7 | @height : Type 8 | 9 | property left, top, width, height 10 | 11 | def initialize() 12 | @left = Type.new(0) 13 | @top = Type.new(0) 14 | @width = Type.new(0) 15 | @height = Type.new(0) 16 | end 17 | 18 | def initialize(@left, @top, @width, @height) 19 | end 20 | 21 | def initialize(pos, size) 22 | @left, @top = pos.x, pos.y 23 | @width, @height = size.x, size.y 24 | end 25 | 26 | def initialize(vec) 27 | @left, @top = vec.x, vec.y 28 | @width, @height = vec.z, vec.w 29 | end 30 | 31 | def contains?(x, y) : Bool 32 | min, max = bounds 33 | x >= min.x && x < max.x && y >= min.y && y < max.y 34 | end 35 | 36 | def contains?(point) : Bool 37 | contains? point.x, point.y 38 | end 39 | 40 | def intersects?(other) : Rect(Type) | Bool 41 | my_min, my_max = bounds 42 | other_min, other_max = other.bounds 43 | 44 | inter_min = VectorImp(Type, 2).new Math.max(my_min.x, other_min.x), Math.max(my_min.y, other_min.y) 45 | inter_max = VectorImp(Type, 2).new Math.min(my_max.x, other_max.x), Math.min(my_max.y, other_max.y) 46 | if inter_min.x < inter_max.x && inter_min.y < inter_max.y 47 | Rect(Type).new(inter_min, inter_max - inter_min) 48 | else 49 | false 50 | end 51 | end 52 | 53 | def bounds 54 | min_x = Math.min @left, @left + @width 55 | max_x = Math.max @left, @left + @width 56 | min_y = Math.min @top, @top + @height 57 | max_y = Math.max @top, @top + @height 58 | return VectorImp(Type, 2).new(min_x, min_y), VectorImp(Type, 2).new(max_x, max_y) 59 | end 60 | 61 | def shrink(amount) 62 | self.class.new @left + amount, @top + amount, @width - amount * 2, @height - amount * 2 63 | end 64 | 65 | def expand(amount) 66 | self.class.new @left - amount, @top - amount, @width + amount * 2, @height + amount * 2 67 | end 68 | 69 | def merge(rect) 70 | width = {@left + @width, rect.left + rect.width}.max 71 | height = {@top + @height, rect.top + rect.height}.max 72 | left = {@left, rect.left}.min 73 | top = {@top, rect.top}.min 74 | width -= left 75 | height -= top 76 | self.class.new left, top, width, height 77 | end 78 | 79 | def merge_relative(rect) 80 | left = {@left + rect.left, @left}.min 81 | top = {@top + rect.top, @top}.min 82 | width = rect.left + rect.width > @width ? rect.left + rect.width : @width 83 | height = rect.top + rect.height > @height ? rect.top + rect.height : @height 84 | self.class.new left, top, width, height 85 | end 86 | 87 | private macro def_conv_meth(name, type) 88 | {% if type == TYPE %} 89 | def {{name}} 90 | self 91 | end 92 | {% else %} 93 | def {{name}} 94 | Rect({{type}}).new( 95 | @left.{{name}}, @top.{{name}}, 96 | @width.{{name}}, @height.{{name}} 97 | ) 98 | end 99 | {% end %} 100 | end 101 | 102 | def_conv_meth(to_i8, Int8) 103 | def_conv_meth(to_i16, Int16) 104 | def_conv_meth(to_i32, Int32) 105 | def_conv_meth(to_i64, Int64) 106 | 107 | def_conv_meth(to_u8, UInt8) 108 | def_conv_meth(to_u16, UInt16) 109 | def_conv_meth(to_u32, UInt32) 110 | def_conv_meth(to_u64, UInt64) 111 | 112 | def_conv_meth(to_f32, Float32) 113 | def_conv_meth(to_f64, Float64) 114 | 115 | def_conv_meth(to_i, Int32) 116 | def_conv_meth(to_u, UInt32) 117 | def_conv_meth(to_f, Float64) 118 | end 119 | 120 | module Boleite 121 | alias Recti8 = Rect(Int8) 122 | alias Recti16 = Rect(Int16) 123 | alias Recti32 = Rect(Int32) 124 | alias Recti64 = Rect(Int64) 125 | 126 | alias Rectf32 = Rect(Float32) 127 | alias Rectf64 = Rect(Float64) 128 | 129 | alias IntRect = Recti32 130 | alias FloatRect = Rectf64 131 | end -------------------------------------------------------------------------------- /src/boleite/math/vector.cr: -------------------------------------------------------------------------------- 1 | module Boleite::Vector 2 | def self.clamp(value : VectorImp(T, N), min : VectorImp(T, N), max : VectorImp(T, N)) forall T, N 3 | left.class.new do |index| 4 | value[index].clamp min[index], max[index] 5 | end 6 | end 7 | 8 | def self.min(left : VectorImp(T, N), right : VectorImp(T, N)) forall T, N 9 | left.class.new do |index| 10 | Math.min left[index], right[index] 11 | end 12 | end 13 | 14 | def self.max(left : VectorImp(T, N), right : VectorImp(T, N)) forall T, N 15 | left.class.new do |index| 16 | Math.max left[index], right[index] 17 | end 18 | end 19 | 20 | def self.magnitude(value : VectorImp(T, N)) forall T, N 21 | Math.sqrt self.square_magnitude(value) 22 | end 23 | 24 | def self.square_magnitude(value : VectorImp(T, N)) forall T, N 25 | value.sum { |val| val * val } 26 | end 27 | 28 | def self.normalize(value : VectorImp(T, N)) forall T, N 29 | magnitude = self.magnitude(value) 30 | value.class.new do |index| 31 | value[index] / magnitude 32 | end 33 | end 34 | 35 | def self.dot(left : VectorImp(T, N), right : VectorImp(T, N)) forall T, N 36 | dot = T.zero 37 | N.times { |index| dot += left[index] * right[index] } 38 | dot 39 | end 40 | 41 | def self.cross(left : VectorImp(T, 3), right : VectorImp(T, 3)) forall T 42 | VectorImp(T, 3).new( 43 | left.y * right.z - left.z * right.y, 44 | left.z * right.x - left.x * right.z, 45 | left.x * right.y - left.y * right.x 46 | ) 47 | end 48 | 49 | def self.rotate(v : VectorImp(T, 2), radians) forall T 50 | VectorImp(T, 2).new( 51 | v.x * T.new(Math.cos(radians)) - v.y * T.new(Math.sin(radians)), 52 | v.x * T.new(Math.sin(radians)) + v.y * T.new(Math.cos(radians)) 53 | ) 54 | end 55 | 56 | def self.inside_shape?(v : Indexable(VectorImp(T, 2)), p : VectorImp(T, 2)) forall T 57 | cn = 0 58 | v.each_index do |i| 59 | n = (i + 1) % v.size 60 | if (v[i].y <= p.y && v[n].y > p.y) || 61 | (v[i].y > p.y && v[n].y <= p.y) 62 | vt = (p.y - v[i].y).to_f / (v[n].y - v[i].y) 63 | cn += 1 if p.x < v[i].x + vt * (v[n].x - v[i].x) 64 | end 65 | end 66 | cn % 2 == 1 67 | end 68 | 69 | def self.distance_to_ray(origin : VectorImp(T, 3), dir : VectorImp(T, 3), point : VectorImp(T, 3)) forall T 70 | cross = self.cross dir, point - origin 71 | self.magnitude cross 72 | end 73 | 74 | def self.closest_point_on_segment(a : VectorImp(T, 3), b : VectorImp(T, 3), point : VectorImp(T, 3), clamp = true) forall T 75 | ap = point - a 76 | ab = b - a 77 | ab2 = square_magnitude ab 78 | ap_ab = dot ap, ab 79 | t = ap_ab / ab2 80 | if clamp 81 | t = T.zero if t < 0 82 | t = T.new 1 if t > 1 83 | end 84 | a + ab * t 85 | end 86 | 87 | def self.distance_to_segment(a : VectorImp(T, 3), b : VectorImp(T, 3), point : VectorImp(T, 3), clamp = true) forall T 88 | closest = closest_point_on_segment a, b, point, clamp 89 | magnitude closest - point 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /src/boleite/math/vector_imp.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::VectorImp(Type, Size) 2 | SIZE = Size 3 | TYPE = Type 4 | 5 | def self.zero : self 6 | self.new(StaticArray(Type, Size).new(Type.new(0))) 7 | end 8 | 9 | def self.one : self 10 | self.new(StaticArray(Type, Size).new(Type.new(1))) 11 | end 12 | 13 | @elements : StaticArray(Type, Size) 14 | 15 | def initialize() 16 | @elements = StaticArray(Type, Size).new(Type.zero) 17 | end 18 | 19 | def initialize(vec : VectorImp(U, Size)) forall U 20 | @elements = StaticArray(Type, Size).new do |index| 21 | Type.new(vec[index]) 22 | end 23 | end 24 | 25 | def initialize(@elements : StaticArray(Type, Size)) 26 | end 27 | 28 | def initialize(elements : Array(Type)) 29 | @elements = StaticArray(Type, Size).new do |index| 30 | elements[index] 31 | end 32 | end 33 | 34 | def initialize(&block) 35 | @elements = StaticArray(Type, Size).new do |index| 36 | yield index 37 | end 38 | end 39 | 40 | def initialize(*args) 41 | @elements = StaticArray(Type, Size).new do |index| 42 | Type.new args[index] 43 | end 44 | end 45 | 46 | def map 47 | cpy = self.class.new do |index| 48 | yield @elements[index] 49 | end 50 | end 51 | 52 | def each 53 | @elements.each { |v| yield v } 54 | end 55 | 56 | def each_index 57 | @elements.each_index { |v| yield v } 58 | end 59 | 60 | def sum 61 | @elements.sum { |v| yield v } 62 | end 63 | 64 | def [](index) 65 | @elements[index] 66 | end 67 | 68 | def []=(index, value) 69 | @elements[index] = value 70 | end 71 | 72 | def ==(other : self) 73 | @elements == other.elements 74 | end 75 | 76 | protected def elements 77 | @elements 78 | end 79 | 80 | private macro def_vector_property(name, index) 81 | def {{name.id}} 82 | @elements[{{index}}] 83 | end 84 | 85 | def {{name.id}}=(val) 86 | @elements[{{index}}] = val 87 | end 88 | end 89 | 90 | def_vector_property(:x, 0) 91 | def_vector_property(:y, 1) 92 | def_vector_property(:z, 2) 93 | def_vector_property(:w, 3) 94 | 95 | def_vector_property(:r, 0) 96 | def_vector_property(:g, 1) 97 | def_vector_property(:b, 2) 98 | def_vector_property(:a, 3) 99 | 100 | private macro def_vector_math(operator, arg_type) 101 | def {{operator.id}}(other : {{arg_type}}) : self 102 | self.class.new().do_math(self, other) do |a, b| 103 | a.{{operator.id}}(b) 104 | end 105 | end 106 | end 107 | 108 | def_vector_math(:+, VectorImp(Type, Size)) 109 | def_vector_math(:-, VectorImp(Type, Size)) 110 | def_vector_math(:*, VectorImp(Type, Size)) 111 | def_vector_math(:/, VectorImp(Type, Size)) 112 | def_vector_math(://, VectorImp(Type, Size)) 113 | def_vector_math(:+, Type) 114 | def_vector_math(:-, Type) 115 | def_vector_math(:*, Type) 116 | def_vector_math(:/, Type) 117 | def_vector_math(://, Type) 118 | 119 | def -() 120 | self.class.new do |index| 121 | -self[index] 122 | end 123 | end 124 | 125 | protected def do_math(a : self, b : self, &block) : self 126 | @elements.each_index do |i| 127 | @elements[i] = yield a[i], b[i] 128 | end 129 | self 130 | end 131 | 132 | protected def do_math(a : self, b : Type, &block) : self 133 | @elements.each_index do |i| 134 | @elements[i] = yield a[i], b 135 | end 136 | self 137 | end 138 | 139 | private macro def_conv_meth(name, type) 140 | {% if type == TYPE %} 141 | def {{name}} 142 | self 143 | end 144 | {% else %} 145 | def {{name}} 146 | VectorImp({{type}}, Size).new( 147 | StaticArray({{type}}, Size).new { |index| 148 | @elements[index].{{name}} 149 | } 150 | ) 151 | end 152 | {% end %} 153 | end 154 | 155 | def_conv_meth(to_i8, Int8) 156 | def_conv_meth(to_i16, Int16) 157 | def_conv_meth(to_i32, Int32) 158 | def_conv_meth(to_i64, Int64) 159 | 160 | def_conv_meth(to_u8, UInt8) 161 | def_conv_meth(to_u16, UInt16) 162 | def_conv_meth(to_u32, UInt32) 163 | def_conv_meth(to_u64, UInt64) 164 | 165 | def_conv_meth(to_f32, Float32) 166 | def_conv_meth(to_f64, Float64) 167 | 168 | def_conv_meth(to_i, Int32) 169 | def_conv_meth(to_u, UInt32) 170 | def_conv_meth(to_f, Float64) 171 | 172 | def to_s(io : IO) 173 | io << "Vec" 174 | io << {% SIZE %} 175 | io << "{" 176 | @elements.join ", ", io, &.inspect(io) 177 | io << "}" 178 | end 179 | end 180 | 181 | module Boleite 182 | alias Vector2i8 = VectorImp(Int8, 2) 183 | alias Vector2i16 = VectorImp(Int16, 2) 184 | alias Vector2i32 = VectorImp(Int32, 2) 185 | alias Vector2i64 = VectorImp(Int64, 2) 186 | alias Vector2u8 = VectorImp(UInt8, 2) 187 | alias Vector2u16 = VectorImp(UInt16, 2) 188 | alias Vector2u32 = VectorImp(UInt32, 2) 189 | alias Vector2u64 = VectorImp(UInt64, 2) 190 | alias Vector2f32 = VectorImp(Float32, 2) 191 | alias Vector2f64 = VectorImp(Float64, 2) 192 | 193 | alias Vector2i = Vector2i32 194 | alias Vector2u = Vector2u32 195 | alias Vector2f = Vector2f64 196 | 197 | alias Vector3i8 = VectorImp(Int8, 3) 198 | alias Vector3i16 = VectorImp(Int16, 3) 199 | alias Vector3i32 = VectorImp(Int32, 3) 200 | alias Vector3i64 = VectorImp(Int64, 3) 201 | alias Vector3u8 = VectorImp(UInt8, 3) 202 | alias Vector3u16 = VectorImp(UInt16, 3) 203 | alias Vector3u32 = VectorImp(UInt32, 3) 204 | alias Vector3u64 = VectorImp(UInt64, 3) 205 | alias Vector3f32 = VectorImp(Float32, 3) 206 | alias Vector3f64 = VectorImp(Float64, 3) 207 | 208 | alias Vector3i = Vector3i32 209 | alias Vector3u = Vector3u32 210 | alias Vector3f = Vector3f64 211 | 212 | alias Vector4i8 = VectorImp(Int8, 4) 213 | alias Vector4i16 = VectorImp(Int16, 4) 214 | alias Vector4i32 = VectorImp(Int32, 4) 215 | alias Vector4i64 = VectorImp(Int64, 4) 216 | alias Vector4u8 = VectorImp(UInt8, 4) 217 | alias Vector4u16 = VectorImp(UInt16, 4) 218 | alias Vector4u32 = VectorImp(UInt32, 4) 219 | alias Vector4u64 = VectorImp(UInt64, 4) 220 | alias Vector4f32 = VectorImp(Float32, 4) 221 | alias Vector4f64 = VectorImp(Float64, 4) 222 | 223 | alias Vector4i = Vector4i32 224 | alias Vector4u = Vector4u32 225 | alias Vector4f = Vector4f64 226 | 227 | alias Color32f = Vector4f32 228 | alias Color64f = Vector4f64 229 | alias Colorf = Color32f 230 | 231 | alias Color8i = Vector4u8 232 | alias Colori = Color8i 233 | end -------------------------------------------------------------------------------- /src/boleite/random.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::Random 2 | include CrystalClear 3 | 4 | @seed : UInt32 5 | 6 | getter seed 7 | 8 | def initialize(@seed) 9 | end 10 | 11 | def get_int : UInt32 12 | generate 13 | end 14 | 15 | ensures return_value >= min 16 | ensures return_value < max 17 | def get_int(min, max) : UInt32 18 | num = generate 19 | num % (max - min) + min 20 | end 21 | 22 | def get_zero_to_one : Float64 23 | generate.to_f / UInt32::MAX 24 | end 25 | 26 | # Method used by standard library 27 | def rand(max : Int) : Int 28 | get_int 0, max 29 | end 30 | 31 | Contracts.ignore_method generate 32 | 33 | protected abstract def generate : UInt32 34 | end 35 | 36 | # Noise function based on Squirrel Eiserloh SquirrelNoise function from GDC17 37 | class Boleite::NoiseRandom < Boleite::Random 38 | # These bit-noise been selected for having interesting/distinctive bits 39 | NOISE1 = 0xb5297a4d 40 | NOISE2 = 0x68e31da4 41 | NOISE3 = 0x1b56c4e9 42 | 43 | @index : UInt32 44 | 45 | getter index 46 | 47 | def initialize(seed, @index = 0u32) 48 | super(seed) 49 | end 50 | 51 | def generate : UInt32 52 | result = generate @index 53 | @index += 1 54 | result 55 | end 56 | 57 | def generate(num) : UInt32 58 | num *= NOISE1 59 | num += @seed 60 | num ^= num >> 8 61 | num += NOISE2 62 | num ^= num << 8 63 | num *= NOISE3 64 | num ^= num >> 8 65 | num 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /src/boleite/serializable.cr: -------------------------------------------------------------------------------- 1 | 2 | module Boleite::Serializable(SerializerType) 3 | def serializer 4 | SerializerType.new 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /src/boleite/serializers/backend/configuration.cr: -------------------------------------------------------------------------------- 1 | class Boleite::BackendConfiguration 2 | struct ObjSerializer 3 | def marshal(obj, node) 4 | node.marshal("gfx", obj.gfx.to_s) 5 | node.marshal("version", obj.version) 6 | node.marshal("video_mode", obj.video_mode) 7 | node.marshal("double_buffering", obj.double_buffering) 8 | node.marshal("multisamples", obj.multisamples) 9 | end 10 | 11 | def unmarshal(node) 12 | config = BackendConfiguration.new() 13 | config.gfx = GfxType.parse(node.unmarshal_string("gfx")) 14 | config.version = node.unmarshal("version", Version) 15 | config.video_mode = node.unmarshal("video_mode", VideoMode) 16 | config.double_buffering = node.unmarshal_bool("double_buffering") 17 | config.multisamples = node.unmarshal_int("multisamples").to_u8 18 | config 19 | end 20 | end 21 | 22 | extend Serializable(ObjSerializer) 23 | end 24 | -------------------------------------------------------------------------------- /src/boleite/serializers/backend/video_mode.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::VideoMode 2 | struct ObjSerializer 3 | def marshal(obj, node) 4 | node.marshal("resolution", obj.resolution) 5 | node.marshal("mode", obj.mode.to_s) 6 | node.marshal("refresh_rate", obj.refresh_rate) 7 | end 8 | 9 | def unmarshal(node) 10 | video_mode = VideoMode.new 11 | video_mode.resolution = node.unmarshal("resolution", Vector2u) 12 | video_mode.mode = Mode.parse(node.unmarshal_string("mode")) 13 | video_mode.refresh_rate = node.unmarshal_int("refresh_rate").to_u16 14 | video_mode 15 | end 16 | end 17 | 18 | extend Serializable(ObjSerializer) 19 | end 20 | -------------------------------------------------------------------------------- /src/boleite/serializers/random.cr: -------------------------------------------------------------------------------- 1 | class Boleite::NoiseRandom 2 | struct ObjSerializer 3 | def marshal(obj, node) 4 | seed = obj.seed.to_i64 5 | index = obj.index.to_i64 6 | node.value = [seed.as(SerializableType), index.as(SerializableType)] 7 | end 8 | 9 | def unmarshal(node) 10 | arr = node.value.as(Array(SerializableType)) 11 | seed = arr[0].as(Int64) 12 | index = arr[1].as(Int64) 13 | NoiseRandom.new seed.to_u32, index.to_u32 14 | end 15 | end 16 | 17 | extend Serializable(ObjSerializer) 18 | end 19 | -------------------------------------------------------------------------------- /src/boleite/serializers/vector.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::VectorImp(Type, Size) 2 | struct ObjSerializer(Type, Size) 3 | def marshal(obj, node) 4 | tmp = Array(SerializableType).new(Size) 5 | obj.elements.each do |value| 6 | tmp << value.to_f 7 | end 8 | node.value = tmp 9 | end 10 | 11 | def unmarshal(node) 12 | arr = node.value.as(Array(SerializableType)) 13 | VectorImp(Type, Size).new do |index| 14 | Type.new(arr[index].as(Number)) 15 | end 16 | end 17 | end 18 | 19 | def self.serializer 20 | ObjSerializer(Type, Size).new 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /src/boleite/serializers/version.cr: -------------------------------------------------------------------------------- 1 | struct Boleite::Version 2 | struct ObjSerializer 3 | def marshal(obj, node) 4 | node.value = [obj.major.to_i64, obj.minor.to_i64, obj.patch.to_i64] of SerializableType 5 | end 6 | 7 | def unmarshal(node) 8 | arr = node.value.as(Array(SerializableType)) 9 | arr = arr.map { |item| item.as(Int64) } 10 | 11 | Version.new(arr[0].to_u8, arr[1].to_u8, arr[2].to_u8) 12 | end 13 | end 14 | 15 | extend Serializable(ObjSerializer) 16 | end 17 | -------------------------------------------------------------------------------- /src/boleite/state.cr: -------------------------------------------------------------------------------- 1 | abstract class Boleite::State 2 | @next : State | Nil 3 | 4 | def initialize 5 | @next = nil 6 | end 7 | 8 | def next 9 | @next 10 | end 11 | 12 | def next=(@next : State | Nil) 13 | end 14 | 15 | def enable 16 | end 17 | 18 | def disable 19 | end 20 | 21 | abstract def update(delta) 22 | abstract def render(delta) 23 | end 24 | -------------------------------------------------------------------------------- /src/boleite/state_stack.cr: -------------------------------------------------------------------------------- 1 | class Boleite::StateStack 2 | include CrystalClear 3 | 4 | def initialize 5 | @state = nil 6 | end 7 | 8 | def empty? 9 | @state.nil? 10 | end 11 | 12 | requires state.nil? == false 13 | def push(state : State) 14 | state.next = @state 15 | if prev_state = @state 16 | prev_state.disable 17 | end 18 | @state = state 19 | state.enable 20 | end 21 | 22 | requires empty? == false 23 | def pop 24 | old_state = @state.as(State) 25 | @state = old_state.next 26 | old_state.next = nil 27 | old_state.disable 28 | if new_state = @state 29 | new_state.enable 30 | end 31 | old_state 32 | end 33 | 34 | requires empty? == false 35 | requires state.nil? == false 36 | def replace(state : State) 37 | pop 38 | push state 39 | end 40 | 41 | def clear 42 | while !empty? 43 | pop 44 | end 45 | end 46 | 47 | requires empty? == false 48 | def top 49 | @state.as(State) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/boleite/version.cr: -------------------------------------------------------------------------------- 1 | 2 | struct Boleite::Version 3 | property :major 4 | property :minor 5 | property :patch 6 | 7 | def initialize() 8 | @major = 0_u8 9 | @minor = 0_u8 10 | @patch = 0_u8 11 | end 12 | 13 | def initialize(major : Int, minor : Int, patch : Int = 0_u8) 14 | @major = major.to_u8 15 | @minor = minor.to_u8 16 | @patch = patch.to_u8 17 | end 18 | 19 | def to_s(io) 20 | io << to_s 21 | end 22 | 23 | def to_s 24 | "#{@major}.#{@minor}.#{@patch}" 25 | end 26 | end 27 | --------------------------------------------------------------------------------