| 1 | module kin::xtk::glass_button |
|---|
| 2 | import kin::io.stdout |
|---|
| 3 | |
|---|
| 4 | import kin::gfx.rgba |
|---|
| 5 | import kin::gfx.rect |
|---|
| 6 | import kin::gfx.linear_gradient |
|---|
| 7 | import kin::gfx.radial_gradient |
|---|
| 8 | |
|---|
| 9 | actor seq_stream ( list ) |
|---|
| 10 | def unary<- ( self ) |
|---|
| 11 | match self.list |
|---|
| 12 | h :: t => |
|---|
| 13 | self become ( list = t ) |
|---|
| 14 | => h |
|---|
| 15 | _ => |
|---|
| 16 | fail #`element |
|---|
| 17 | |
|---|
| 18 | actor button_model ( text ) |
|---|
| 19 | let state = `nominal |
|---|
| 20 | let on_clicked = kin::xtk.event_delegate() |
|---|
| 21 | |
|---|
| 22 | def hover ( self ) |
|---|
| 23 | match self.state |
|---|
| 24 | `hover => false |
|---|
| 25 | `pressed => false |
|---|
| 26 | _ => |
|---|
| 27 | self become ( state = `hover ) |
|---|
| 28 | => true |
|---|
| 29 | |
|---|
| 30 | def press ( self ) |
|---|
| 31 | match self.state |
|---|
| 32 | `pressed => false |
|---|
| 33 | _ => |
|---|
| 34 | self become ( state = `pressed ) |
|---|
| 35 | => true |
|---|
| 36 | |
|---|
| 37 | def release ( self ) |
|---|
| 38 | match self.state |
|---|
| 39 | `hover => false |
|---|
| 40 | `pressed => |
|---|
| 41 | self become ( state = `hover ) |
|---|
| 42 | self.on_clicked.propagate ( self ) |
|---|
| 43 | _ => |
|---|
| 44 | self become ( state = `hover ) |
|---|
| 45 | => true |
|---|
| 46 | |
|---|
| 47 | def reset ( self ) |
|---|
| 48 | match self.state |
|---|
| 49 | `hover => |
|---|
| 50 | self become ( state = `nominal ) |
|---|
| 51 | => true |
|---|
| 52 | _ => false |
|---|
| 53 | |
|---|
| 54 | def set_text ( self, text ) |
|---|
| 55 | self become ( text = text ) |
|---|
| 56 | => true |
|---|
| 57 | |
|---|
| 58 | actor button_presenter ( model ) |
|---|
| 59 | let width = 100.0 |
|---|
| 60 | let height = 27.0 |
|---|
| 61 | let x = 0.0 |
|---|
| 62 | let y = 0.0 |
|---|
| 63 | |
|---|
| 64 | def paint ( self, window, gfx ) => self.draw(gfx) |
|---|
| 65 | |
|---|
| 66 | def draw ( self, gfx ) |
|---|
| 67 | let initial_trans = gfx.transform |
|---|
| 68 | |
|---|
| 69 | gfx.set_transform ( initial_trans.translate ( self.x, self.y ) ) |
|---|
| 70 | |
|---|
| 71 | draw_button ( gfx, self.width, self.height, self.model ) |
|---|
| 72 | |
|---|
| 73 | gfx.set_transform ( initial_trans ) |
|---|
| 74 | |
|---|
| 75 | def desired_width ( self, gfx, mode ) |
|---|
| 76 | let text_width = gfx.text_bounds ( font, self.model.text ).width |
|---|
| 77 | |
|---|
| 78 | => text_width + 8.0 |
|---|
| 79 | |
|---|
| 80 | def desired_height ( self, gfx, mode ) |
|---|
| 81 | => 28.0 |
|---|
| 82 | |
|---|
| 83 | def set_bounds ( self, x:real64, y:real64, width:real64, height:real64 ) |
|---|
| 84 | => self become ( x = x, y = y, width = width, height = height ) |
|---|
| 85 | |
|---|
| 86 | def handle_event ( self, event ) |
|---|
| 87 | match ( self.x <= event.x ) & ( self.x + self.width >= event.x ) & ( self.y <= event.y ) & ( self.y + self.height >= event.y ) |
|---|
| 88 | false => self.model.reset() |
|---|
| 89 | |
|---|
| 90 | match event |
|---|
| 91 | _ : kin::xtk.button_press_event => self.model.press() |
|---|
| 92 | _ : kin::xtk.button_release_event => self.model.release() |
|---|
| 93 | _ => self.model.hover() |
|---|
| 94 | |
|---|
| 95 | let pure_background = rgba ( 0xffffffff ) |
|---|
| 96 | let background = pure_background.scale ( 0x7f ) |
|---|
| 97 | let black = rgba ( 0x000000ff ) |
|---|
| 98 | let white = rgba ( 0xffffffff ) |
|---|
| 99 | let font = kin::gfx.load_font ( "FreeSans").with_height(12.0) |
|---|
| 100 | #~ let font = kin::gfx.load_font ( "Inconsolata" ).with_height(14).with_hinting(true).with_native_rendering(true) |
|---|
| 101 | let font_no_hint = font.with_hinting(false) |
|---|
| 102 | |
|---|
| 103 | def draw_button ( gfx, width, height, button ) |
|---|
| 104 | let middle = height * 0.5 |
|---|
| 105 | let initial_transform = gfx.transform |
|---|
| 106 | |
|---|
| 107 | match button.state |
|---|
| 108 | `pressed => |
|---|
| 109 | gfx.set_transform ( gfx.transform.translate ( 0.0, 1.0 ) ) |
|---|
| 110 | |
|---|
| 111 | # main gradient - actually only one stripe |
|---|
| 112 | gfx.fill_rect ( background.scale ( 0x9a ), 2.0, 0.0, width - 4.0, middle ) |
|---|
| 113 | gfx.fill_rect ( background.scale ( 0x6f ), 2.0, middle, width - 4.0, 1.0 ) |
|---|
| 114 | gfx.fill_rect ( background.scale ( 0x45 ), 2.0, middle + 1.0, width - 4.0, height - middle - 4.0 ) |
|---|
| 115 | |
|---|
| 116 | # outline of button |
|---|
| 117 | let outline = background.scale ( 0x42 ) |
|---|
| 118 | |
|---|
| 119 | gfx.fill_rect ( outline, 2.0, 0.0, width - 4.0, 1.0 ) |
|---|
| 120 | gfx.fill_rect ( outline, 2.0, height - 2.0, width - 4.0, 1.0 ) |
|---|
| 121 | gfx.fill_rect ( outline, 0.0, 2.0, 1.0, height - 5.0 ) |
|---|
| 122 | gfx.fill_rect ( outline, width - 1.0, 2.0, 1.0, height - 5.0 ) |
|---|
| 123 | gfx.fill_rect ( outline, 1.0, 1.0, 1.0, 1.0 ) |
|---|
| 124 | gfx.fill_rect ( outline, width - 2.0, 1.0, 1.0, 1.0 ) |
|---|
| 125 | |
|---|
| 126 | # inner border |
|---|
| 127 | let inner = background.scale ( 0x40 ) |
|---|
| 128 | |
|---|
| 129 | let g1 = linear_gradient ( 1.0, 1.0, 1.0, middle, [ background, inner ], [ 0.0, 1.0 ] ) |
|---|
| 130 | |
|---|
| 131 | gfx.fill_rect ( g1, 2.0, 1.0, width - 4.0, 1.0 ) |
|---|
| 132 | gfx.fill_rect ( g1, 1.0, 2.0, 1.0, middle - 2.0 ) |
|---|
| 133 | gfx.fill_rect ( g1, width - 2.0, 2.0, 1.0, middle - 2.0 ) |
|---|
| 134 | gfx.fill_rect ( inner, 1.0, middle, 1.0, middle - 2.0 ) |
|---|
| 135 | gfx.fill_rect ( inner, width - 2.0, middle, 1.0, middle - 2.0 ) |
|---|
| 136 | |
|---|
| 137 | let g3 = radial_gradient ( 0.5 * width, height + 60.0, 100.0, [ background, inner ], [ 0.0, 1.0 ] ) |
|---|
| 138 | |
|---|
| 139 | gfx.fill_rect ( g3, 2.0, height - 3.0, width - 4.0, 1.000 ) |
|---|
| 140 | |
|---|
| 141 | # bottom shading |
|---|
| 142 | let radius = width + height |
|---|
| 143 | |
|---|
| 144 | match button.state |
|---|
| 145 | `hover => |
|---|
| 146 | let g3 = radial_gradient ( 0.5 * width, radius, radius, [ rgba ( 0xffffff7f ), rgba ( 0xffffff1f ) ], [ 0.0, 1.0 ] ) |
|---|
| 147 | gfx.fill_rect ( g3, 2.0, 2.0, width - 4.0, height - 5.0 ) |
|---|
| 148 | `pressed => |
|---|
| 149 | let g3 = radial_gradient ( 0.5 * width, radius, radius, [ rgba ( 0xffcf00ff ), rgba ( 0xffcf000f ) ], [ 0.0, 1.0 ] ) |
|---|
| 150 | gfx.fill_rect ( g3, 2.0, 2.0, width - 4.0, height - 5.0 ) |
|---|
| 151 | |
|---|
| 152 | # text |
|---|
| 153 | let text_width = gfx.text_bounds ( font, button.text ).width |
|---|
| 154 | |
|---|
| 155 | gfx.set_transform ( gfx.transform.translate ( ( width - text_width ) * 0.5, 18.0 ) ) |
|---|
| 156 | |
|---|
| 157 | gfx.draw_text ( font, white, button.text, 0.0, 0.0 ) |
|---|
| 158 | |
|---|
| 159 | gfx.set_transform ( initial_transform ) |
|---|
| 160 | |
|---|
| 161 | def draw_buttons ( out, gfx, buttons ) |
|---|
| 162 | gfx.clear ( rgba ( 0x0000007f ) ) |
|---|
| 163 | |
|---|
| 164 | fold ( ( x, btn ) => btn.set_bounds ( x, 0.0, btn.desired_width ( gfx, `normal ), btn.desired_height ( gfx, `normal ) ).x + btn.width + 4.0, 0.0, buttons ) |
|---|
| 165 | |
|---|
| 166 | #~ gfx.clear ( rgba ( 0xffffffff ) ) |
|---|
| 167 | gfx.fill_rect ( rgba ( 0x00001fff ), 0.0, 0.0, real64 ( gfx.width ), 28.0 ) |
|---|
| 168 | |
|---|
| 169 | foreach ( ( b ) => b.draw(gfx), buttons ) |
|---|
| 170 | |
|---|
| 171 | let text = "Can you tell the difference with or without hinting?" |
|---|
| 172 | |
|---|
| 173 | gfx.draw_text ( font, white, text ++ " - with ", 60.0, 60.0 ) |
|---|
| 174 | |
|---|
| 175 | gfx.draw_text ( font_no_hint, white, text ++ " - without", 60.0, 80.0 ) |
|---|
| 176 | |
|---|
| 177 | gfx.set_transform ( gfx.transform.translate(0,300).scale( 4, 4 ).rotate ( 0.2 ) ) |
|---|
| 178 | |
|---|
| 179 | #~ gfx.set_transform ( gfx.transform.scale( 4, 4 ).translate ( 30, 30 ) ) |
|---|
| 180 | |
|---|
| 181 | map ( ( b ) => b.draw(gfx), buttons ) |
|---|
| 182 | |
|---|
| 183 | gfx.set_transform ( kin::gfx.identity.rotate ( -0.7 ).translate ( 300.0, 30.0 ) ) |
|---|
| 184 | |
|---|
| 185 | draw_float_box ( gfx, 120, 30, rgba ( 0x8080ffff ) ) |
|---|
| 186 | |
|---|
| 187 | let triangle = kin::gfx.path().move_to ( 120, 0 ).line_to ( 105, 15 ).line_to( 120, 30 ).close() |
|---|
| 188 | |
|---|
| 189 | gfx.fill ( white, triangle ) |
|---|
| 190 | |
|---|
| 191 | gfx.draw_text ( font, white, "random tag", 6.0, 20.0 ) |
|---|
| 192 | |
|---|
| 193 | gfx.set_transform ( kin::gfx.identity.translate ( 300, 200 ) ) |
|---|
| 194 | |
|---|
| 195 | let or_gate = kin::gfx.path().move_to (0,0).quadratic_to(70,0,100,50).quadratic_to(70,100,0,100).quadratic_to(25,50,0,0).close() |
|---|
| 196 | |
|---|
| 197 | let g3 = linear_gradient ( 0, 0, 0, 100, [ white, black, black, white ], [ 0.0, 0.4, 0.6, 1.0 ] ) |
|---|
| 198 | |
|---|
| 199 | gfx.fill ( black, or_gate ) |
|---|
| 200 | gfx.stroke ( 4, g3, or_gate ) |
|---|
| 201 | |
|---|
| 202 | #~ out <- font <- endl |
|---|
| 203 | #~ let text_bounds = gfx.text_bounds ( font, button.text ) |
|---|
| 204 | #~ out <- text_bounds <- endl |
|---|
| 205 | |
|---|
| 206 | let shadow_colours = [ rgba ( 0x0000007f ), rgba ( 0x00000000 ) ] |
|---|
| 207 | let shadow_values = [ 0.0, 1.0 ] |
|---|
| 208 | |
|---|
| 209 | # not how it would really work - I'd like a 2.5D API which does the usual |
|---|
| 210 | # blur alpha drop shadow |
|---|
| 211 | def draw_float_box ( gfx, width:real64, height:real64, fill ) |
|---|
| 212 | let g1 = linear_gradient ( 0.0, 0.0, 0.0, 10.0, shadow_colours, shadow_values ) |
|---|
| 213 | let g2 = radial_gradient ( 0.0, 0.0, 10.0, shadow_colours, shadow_values ) |
|---|
| 214 | let g3 = linear_gradient ( 0.0, 0.0, 10.0, 0.0, shadow_colours, shadow_values ) |
|---|
| 215 | |
|---|
| 216 | let initial_trans = gfx.transform |
|---|
| 217 | let trans = gfx.transform.inverse().translate ( 0, -4 ).inverse() |
|---|
| 218 | |
|---|
| 219 | gfx.set_transform ( trans ) |
|---|
| 220 | gfx.fill_rect ( g2, 0.0, 0.0, -10, -10 ) |
|---|
| 221 | |
|---|
| 222 | gfx.set_transform ( trans.scale ( 1.0, -1.0 ) ) |
|---|
| 223 | gfx.fill_rect ( g1, 0.0, 0.0, width, 10 ) |
|---|
| 224 | |
|---|
| 225 | gfx.set_transform ( trans.translate ( 0.0, height ) ) |
|---|
| 226 | gfx.fill_rect ( g1, 0.0, 0.0, width, 10 ) |
|---|
| 227 | gfx.fill_rect ( g2, 0.0, 0.0, -10, 10 ) |
|---|
| 228 | |
|---|
| 229 | gfx.set_transform ( trans.scale ( -1.0, 1.0 ) ) |
|---|
| 230 | gfx.fill_rect ( g3, 0.0, 0.0, 10, height ) |
|---|
| 231 | |
|---|
| 232 | gfx.set_transform ( trans.translate ( width, 0.0 ) ) |
|---|
| 233 | gfx.fill_rect ( g3, 0.0, 0.0, 10, height ) |
|---|
| 234 | gfx.fill_rect ( g2, 0.0, 0.0, 10, -10 ) |
|---|
| 235 | |
|---|
| 236 | gfx.set_transform ( trans.translate ( width, height ) ) |
|---|
| 237 | gfx.fill_rect ( g2, 0.0, 0.0, 10, 10 ) |
|---|
| 238 | |
|---|
| 239 | gfx.set_transform ( trans ) |
|---|
| 240 | gfx.fill_rect ( rgba ( 0x0000007f ), -0.2, -0.2, width + 0.4, height + 0.4 ) |
|---|
| 241 | |
|---|
| 242 | gfx.set_transform ( initial_trans ) |
|---|
| 243 | gfx.fill_rect ( fill, 0, 0, width, height ) |
|---|
| 244 | |
|---|
| 245 | def update_buttons ( out, window, buttons, event ) |
|---|
| 246 | let refresh_required = fold ( ( refresh, button ) => update_button ( out, window, button, event ) | refresh, false, buttons ) |
|---|
| 247 | |
|---|
| 248 | match refresh_required |
|---|
| 249 | true => window.refresh() |
|---|
| 250 | |
|---|
| 251 | def update_button ( out, window, button, event ) |
|---|
| 252 | => button.handle_event(event) |
|---|
| 253 | |
|---|
| 254 | let messages = seq_stream ( [ "no, really", "please, no", "please", "...", ". . .", "last chance!" ] ) |
|---|
| 255 | |
|---|
| 256 | def do_press ( window, button ) |
|---|
| 257 | window.retitle ( "Ouch!" ) |
|---|
| 258 | button.set_text ( <-messages ) |
|---|
| 259 | window.refresh() |
|---|
| 260 | #~ button.on_clicked.add ( ( x ) => window.dispose() ) |
|---|
| 261 | #~ button.on_clicked.add ( ( x ) => kin::xtk.halt() ) |
|---|
| 262 | #~ button.on_clicked.add ( ( x ) => kin::core.exit ( 0 ) ) |
|---|
| 263 | |
|---|
| 264 | def create_button ( label ) => button_presenter ( button_model ( label ) ) |
|---|
| 265 | |
|---|
| 266 | def main ( in, out ) |
|---|
| 267 | let window = kin::xtk.create_window ( `normal, rect ( 0, 0, 600, 400 ) ) |
|---|
| 268 | let button = button_model ( "do not press" ) |
|---|
| 269 | let buttons = button_presenter ( button ) :: map ( (txt)=> button_presenter ( button_model ( txt ) ), [ "and", "the", "other", "ones" ] ) |
|---|
| 270 | |
|---|
| 271 | window.retitle ( "glass button" ) |
|---|
| 272 | |
|---|
| 273 | window.set_painter ( ( gfx ) => draw_buttons ( out, gfx, buttons ) ) |
|---|
| 274 | window.resize( +600, +400 ) |
|---|
| 275 | |
|---|
| 276 | let updater = ( event ) => update_buttons ( out, window, buttons, event ) |
|---|
| 277 | |
|---|
| 278 | window.on_pointer_moved.add ( updater ) |
|---|
| 279 | window.on_pointer_exited.add ( updater ) |
|---|
| 280 | window.on_button_pressed.add ( updater ) |
|---|
| 281 | window.on_button_released.add ( updater ) |
|---|
| 282 | |
|---|
| 283 | window.on_key_pressed.add ( ( event ) => out <- "PRESS: " <- event.keysymbol <- " " <- event.keychar <- " " <- event.timestamp <- endl ) |
|---|
| 284 | window.on_key_released.add ( ( event ) => out <- "RELEASE: " <- event.keysymbol <- " " <- event.keychar <- " " <- event.timestamp <- endl ) |
|---|
| 285 | |
|---|
| 286 | window.show() |
|---|
| 287 | |
|---|
| 288 | button.on_clicked.add ( ( button ) => do_press ( window, button ) ) |
|---|
| 289 | |
|---|
| 290 | kin::xtk.event_loop() |
|---|