sketchingpy.sketch2dapp
Pygame-based renderer for Sketchingpy.
License:
BSD
1"""Pygame-based renderer for Sketchingpy. 2 3License: 4 BSD 5""" 6 7import contextlib 8import copy 9import math 10import typing 11 12import PIL.Image 13import PIL.ImageFont 14 15with contextlib.redirect_stdout(None): 16 import pygame 17 import pygame.draw 18 import pygame.image 19 import pygame.key 20 import pygame.locals 21 import pygame.mouse 22 import pygame.time 23 24 25ui_available = False 26try: 27 import pygame_gui # type: ignore 28 import pygame_gui.windows # type: ignore 29 import sketchingpy.pygame_prompt # type: ignore 30 ui_available = True 31except: 32 pass 33 34import sketchingpy.abstracted 35import sketchingpy.const 36import sketchingpy.control_struct 37import sketchingpy.data_struct 38import sketchingpy.local_data_struct 39import sketchingpy.pillow_util 40import sketchingpy.sketch2d_keymap 41import sketchingpy.state_struct 42import sketchingpy.transform 43 44DEFAULT_FPS = 20 45MANUAL_OFFSET = True 46OPTIONAL_SKETCH_CALLBACK = typing.Optional[typing.Callable[[sketchingpy.abstracted.Sketch], None]] 47 48 49class Sketch2DApp(sketchingpy.abstracted.Sketch): 50 """Create a new Pygame-based Sketch.""" 51 52 def __init__(self, width: int, height: int, title: typing.Optional[str] = None, 53 loading_src: typing.Optional[str] = None): 54 """Create a enw Pygame-based sketch. 55 56 Args: 57 width: The width of the sketch in pixels. This will be used for window width. 58 height: The height of the sketch in pixels. This will be used for window height. 59 title: Starting title for the application. 60 loading_src: ID for loading screen. Ignored, reserved for future use. 61 """ 62 super().__init__() 63 64 # System params 65 self._width = width 66 self._height = height 67 68 # Callbacks 69 self._callback_step: OPTIONAL_SKETCH_CALLBACK = None 70 self._callback_quit: OPTIONAL_SKETCH_CALLBACK = None 71 72 # User configurable state 73 self._state_frame_rate = DEFAULT_FPS 74 75 # Buffers 76 self._internal_surface = None 77 self._output_surface = None 78 self._buffers: typing.Dict[str, pygame.Surface] = {} 79 80 # Internal state 81 self._internal_pre_show_actions: typing.List[typing.Callable] = [] 82 self._internal_quit_requested = False 83 self._internal_clock = pygame.time.Clock() 84 self._transformer = sketchingpy.transform.Transformer() 85 self._transformer_stack: typing.List[sketchingpy.transform.Transformer] = [] 86 self._dialog_layer: typing.Optional['AppDialogLayer'] = None 87 88 # Inputs 89 self._mouse = PygameMouse() 90 self._keyboard = PygameKeyboard() 91 92 # Internal struct 93 self._struct_event_handlers = { 94 pygame.KEYDOWN: lambda x: self._process_key_down(x), 95 pygame.KEYUP: lambda x: self._process_key_up(x), 96 pygame.MOUSEBUTTONDOWN: lambda x: self._process_mouse_down(x), 97 pygame.MOUSEBUTTONUP: lambda x: self._process_mouse_up(x), 98 pygame.locals.QUIT: lambda x: self._process_quit(x) 99 } 100 101 # Default window properties 102 self.set_title('Sketchingpy Sketch' if title is None else title) 103 104 ########## 105 # Buffer # 106 ########## 107 108 def create_buffer(self, name: str, width: int, height: int, 109 background: typing.Optional[str] = None): 110 def execute(): 111 has_alpha = self._get_is_color_transparent(background) 112 self._buffers[name] = self._make_shape_surface( 113 pygame.Rect(0, 0, width, height), 114 0, 115 has_alpha=has_alpha 116 ) 117 if not has_alpha: 118 self._buffers[name].fill(pygame.Color(background)) 119 120 if self._internal_surface is None: 121 self._internal_pre_show_actions.append(execute) 122 else: 123 execute() 124 125 def enter_buffer(self, name: str): 126 def execute(): 127 self._internal_surface = self._buffers[name] 128 129 if self._internal_surface is None: 130 self._internal_pre_show_actions.append(execute) 131 else: 132 execute() 133 134 def exit_buffer(self): 135 def execute(): 136 self._internal_surface = self._output_surface 137 138 if self._internal_surface is None: 139 self._internal_pre_show_actions.append(execute) 140 else: 141 execute() 142 143 def draw_buffer(self, x: float, y: float, name: str): 144 def execute(): 145 target_surface = self._buffers[name] 146 147 original_rect = target_surface.get_rect() 148 rect = pygame.Rect( 149 original_rect.x, 150 original_rect.y, 151 original_rect.width, 152 original_rect.height 153 ) 154 rect.left = x 155 rect.top = y 156 157 self._blit_with_transform( 158 target_surface, 159 rect.centerx, 160 rect.centery, 161 self._transformer.quick_copy() 162 ) 163 164 if self._internal_surface is None: 165 self._internal_pre_show_actions.append(execute) 166 else: 167 execute() 168 169 ############ 170 # Controls # 171 ############ 172 173 def get_keyboard(self) -> typing.Optional[sketchingpy.control_struct.Keyboard]: 174 return self._keyboard 175 176 def get_mouse(self) -> typing.Optional[sketchingpy.control_struct.Mouse]: 177 return self._mouse 178 179 ######## 180 # Data # 181 ######## 182 183 def get_data_layer(self) -> typing.Optional[sketchingpy.data_struct.DataLayer]: 184 return sketchingpy.local_data_struct.LocalDataLayer() 185 186 ########### 187 # Dialogs # 188 ########### 189 190 def get_dialog_layer(self) -> typing.Optional[sketchingpy.dialog_struct.DialogLayer]: 191 if not ui_available: 192 return None 193 194 if self._dialog_layer is None: 195 self._dialog_layer = AppDialogLayer(self) 196 197 return self._dialog_layer 198 199 ########### 200 # Drawing # 201 ########### 202 203 def clear(self, color_hex: str): 204 if self._internal_surface is None: 205 self._internal_pre_show_actions.append(lambda: self.clear(color_hex)) 206 return 207 208 self._internal_surface.fill(pygame.Color(color_hex)) 209 210 def draw_arc(self, x1: float, y1: float, x2: float, y2: float, a1: float, 211 a2: float): 212 state_machine = self._get_current_state_machine() 213 214 stroke_enabled = state_machine.get_stroke_enabled() 215 fill_enabled = state_machine.get_fill_enabled() 216 stroke_native = state_machine.get_stroke_native() 217 fill_native = state_machine.get_fill_native() 218 stroke_weight = state_machine.get_stroke_weight() 219 220 mode_native = state_machine.get_arc_mode_native() 221 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 222 223 a1_rad = self._convert_to_radians(a1) 224 a2_rad = self._convert_to_radians(a2) 225 226 transformer = self._transformer.quick_copy() 227 228 def execute_draw(): 229 pillow_util_image = sketchingpy.pillow_util.make_arc_image( 230 rect.x, 231 rect.y, 232 rect.w, 233 rect.h, 234 a1_rad, 235 a2_rad, 236 stroke_enabled, 237 fill_enabled, 238 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 239 self._to_pillow_rgba(fill_native) if fill_enabled else None, 240 stroke_weight 241 ) 242 243 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 244 245 self._blit_with_transform( 246 native_image, 247 rect.centerx, 248 rect.centery, 249 transformer 250 ) 251 252 if self._internal_surface is None: 253 self._internal_pre_show_actions.append(execute_draw) 254 else: 255 execute_draw() 256 257 def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float): 258 state_machine = self._get_current_state_machine() 259 260 stroke_enabled = state_machine.get_stroke_enabled() 261 fill_enabled = state_machine.get_fill_enabled() 262 stroke_native = state_machine.get_stroke_native() 263 fill_native = state_machine.get_fill_native() 264 stroke_weight = state_machine.get_stroke_weight() 265 266 mode_native = state_machine.get_ellipse_mode_native() 267 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 268 269 transformer = self._transformer.quick_copy() 270 271 def execute_draw(): 272 pillow_util_image = sketchingpy.pillow_util.make_ellipse_image( 273 rect.x, 274 rect.y, 275 rect.w, 276 rect.h, 277 stroke_enabled, 278 fill_enabled, 279 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 280 self._to_pillow_rgba(fill_native) if fill_enabled else None, 281 stroke_weight 282 ) 283 284 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 285 286 self._blit_with_transform( 287 native_image, 288 rect.centerx, 289 rect.centery, 290 transformer 291 ) 292 293 if self._internal_surface is None: 294 self._internal_pre_show_actions.append(execute_draw) 295 else: 296 execute_draw() 297 298 def draw_line(self, x1: float, y1: float, x2: float, y2: float): 299 state_machine = self._get_current_state_machine() 300 if not state_machine.get_stroke_enabled(): 301 return 302 303 stroke_color = state_machine.get_stroke_native() 304 stroke_weight = state_machine.get_stroke_weight_native() 305 306 transformer = self._transformer.quick_copy() 307 308 def execute_draw(): 309 min_x = min([x1, x2]) 310 max_x = max([x1, x2]) 311 width = max_x - min_x + 2 * stroke_weight 312 313 min_y = min([y1, y2]) 314 max_y = max([y1, y2]) 315 height = max_y - min_y + 2 * stroke_weight 316 317 rect = pygame.Rect(0, 0, width, height) 318 target_surface = self._make_shape_surface(rect, stroke_weight) 319 320 def adjust(target): 321 return ( 322 target[0] - min_x + stroke_weight - 1, 323 target[1] - min_y + stroke_weight - 1, 324 ) 325 326 pygame.draw.line( 327 target_surface, 328 stroke_color, 329 adjust((x1, y1)), 330 adjust((x2, y2)), 331 width=stroke_weight 332 ) 333 334 center_x = (max_x + min_x) / 2 335 center_y = (max_y + min_y) / 2 336 self._blit_with_transform(target_surface, center_x, center_y, transformer) 337 338 if self._internal_surface is None: 339 self._internal_pre_show_actions.append(execute_draw) 340 else: 341 execute_draw() 342 343 def draw_rect(self, x1: float, y1: float, x2: float, y2: float): 344 state_machine = self._get_current_state_machine() 345 346 stroke_enabled = state_machine.get_stroke_enabled() 347 fill_enabled = state_machine.get_fill_enabled() 348 stroke_native = state_machine.get_stroke_native() 349 fill_native = state_machine.get_fill_native() 350 stroke_weight = state_machine.get_stroke_weight() 351 352 mode_native = state_machine.get_rect_mode_native() 353 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 354 355 transformer = self._transformer.quick_copy() 356 357 def execute_draw(): 358 pillow_util_image = sketchingpy.pillow_util.make_rect_image( 359 rect.x, 360 rect.y, 361 rect.w, 362 rect.h, 363 stroke_enabled, 364 fill_enabled, 365 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 366 self._to_pillow_rgba(fill_native) if fill_enabled else None, 367 stroke_weight 368 ) 369 370 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 371 372 self._blit_with_transform( 373 native_image, 374 rect.centerx, 375 rect.centery, 376 transformer 377 ) 378 379 if self._internal_surface is None: 380 self._internal_pre_show_actions.append(execute_draw) 381 else: 382 execute_draw() 383 384 def draw_shape(self, shape: sketchingpy.shape_struct.Shape): 385 if not shape.get_is_finished(): 386 raise RuntimeError('Finish your shape before drawing.') 387 388 state_machine = self._get_current_state_machine() 389 390 stroke_enabled = state_machine.get_stroke_enabled() 391 fill_enabled = state_machine.get_fill_enabled() 392 stroke_native = state_machine.get_stroke_native() 393 fill_native = state_machine.get_fill_native() 394 stroke_weight = state_machine.get_stroke_weight() 395 396 transformer = self._transformer.quick_copy() 397 398 def execute_draw(): 399 pillow_util_image = sketchingpy.pillow_util.make_shape_image( 400 shape, 401 stroke_enabled, 402 fill_enabled, 403 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 404 self._to_pillow_rgba(fill_native) if fill_enabled else None, 405 stroke_weight 406 ) 407 408 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 409 410 min_x = shape.get_min_x() 411 max_x = shape.get_max_x() 412 center_x = (max_x + min_x) / 2 413 414 min_y = shape.get_min_y() 415 max_y = shape.get_max_y() 416 center_y = (max_y + min_y) / 2 417 418 self._blit_with_transform( 419 native_image, 420 center_x, 421 center_y, 422 transformer 423 ) 424 425 if self._internal_surface is None: 426 self._internal_pre_show_actions.append(execute_draw) 427 else: 428 execute_draw() 429 430 def draw_text(self, x: float, y: float, content: str): 431 content = str(content) 432 state_machine = self._get_current_state_machine() 433 434 stroke_enabled = state_machine.get_stroke_enabled() 435 fill_enabled = state_machine.get_fill_enabled() 436 stroke_native = state_machine.get_stroke_native() 437 fill_native = state_machine.get_fill_native() 438 stroke_weight = state_machine.get_stroke_weight() 439 440 text_font = state_machine.get_text_font_native() 441 fill_pillow = self._to_pillow_rgba(fill_native) 442 stroke_pillow = self._to_pillow_rgba(stroke_native) 443 444 align_info = state_machine.get_text_align_native() 445 anchor_str = align_info.get_horizontal_align() + align_info.get_vertical_align() 446 447 transformer = self._transformer.quick_copy() 448 449 def execute_draw(): 450 pillow_util_image = sketchingpy.pillow_util.make_text_image( 451 x, 452 y, 453 content, 454 text_font, 455 stroke_enabled, 456 fill_enabled, 457 stroke_pillow, 458 fill_pillow, 459 stroke_weight, 460 anchor_str 461 ) 462 463 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 464 465 self._blit_with_transform( 466 native_image, 467 pillow_util_image.get_x() + pillow_util_image.get_width() / 2, 468 pillow_util_image.get_y() + pillow_util_image.get_height() / 2, 469 transformer 470 ) 471 472 if self._internal_surface is None: 473 self._internal_pre_show_actions.append(execute_draw) 474 else: 475 execute_draw() 476 477 ########## 478 # Events # 479 ########## 480 481 def on_step(self, callback: sketchingpy.abstracted.StepCallback): 482 self._callback_step = callback 483 484 def on_quit(self, callback: sketchingpy.abstracted.QuitCallback): 485 self._callback_quit = callback 486 487 ######### 488 # Image # 489 ######### 490 491 def get_image(self, src: str) -> sketchingpy.abstracted.Image: 492 return PygameImage(src) 493 494 def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image): 495 if not image.get_is_loaded(): 496 return 497 498 transformer = self._transformer.quick_copy() 499 500 image_mode_native = self._get_current_state_machine().get_image_mode_native() 501 502 def execute_draw(): 503 rect = self._build_rect_with_mode( 504 x, 505 y, 506 image.get_width(), 507 image.get_height(), 508 image_mode_native 509 ) 510 511 surface = image.get_native() 512 self._blit_with_transform(surface, rect.centerx, rect.centery, transformer) 513 514 if self._internal_surface is None: 515 self._internal_pre_show_actions.append(execute_draw) 516 else: 517 execute_draw() 518 519 def save_image(self, path: str): 520 def execute_save(): 521 pygame.image.save(self._internal_surface, path) 522 523 if self._internal_surface is None: 524 self._internal_pre_show_actions.append(execute_save) 525 self.show_and_quit() 526 else: 527 execute_save() 528 529 ######### 530 # State # 531 ######### 532 533 def push_transform(self): 534 self._transformer_stack.append(copy.deepcopy(self._transformer)) 535 536 def pop_transform(self): 537 if len(self._transformer_stack) == 0: 538 raise RuntimeError('Transformation stack empty.') 539 540 self._transformer = self._transformer_stack.pop() 541 542 ########## 543 # System # 544 ########## 545 546 def get_native(self): 547 if self._internal_surface is None: 548 raise RuntimeError('Need to show sketch first before surface is available.') 549 550 return self._internal_surface 551 552 def set_fps(self, rate: int): 553 self._state_frame_rate = rate 554 555 def set_title(self, title: str): 556 def execute(): 557 pygame.display.set_caption(title) 558 559 if self._internal_surface is None: 560 self._internal_pre_show_actions.append(execute) 561 else: 562 execute() 563 564 def quit(self): 565 self._internal_quit_requested = True 566 567 def show(self, ax=None): 568 self._show_internal(ax=ax, quit_immediately=False) 569 570 def show_and_quit(self, ax=None): 571 self._show_internal(ax=ax, quit_immediately=True) 572 573 ############# 574 # Transform # 575 ############# 576 577 def translate(self, x: float, y: float): 578 self._transformer.translate(x, y) 579 580 def rotate(self, angle_mirror: float): 581 angle = -1 * angle_mirror 582 angle_rad = self._convert_to_radians(angle) 583 self._transformer.rotate(angle_rad) 584 585 def scale(self, scale: float): 586 self._transformer.scale(scale) 587 588 ########### 589 # Support # 590 ########### 591 592 def _get_window_size(self) -> typing.Tuple[int, int]: 593 return (self._width, self._height) 594 595 def _show_internal(self, ax=None, quit_immediately=False): 596 self._snapshot_time() 597 pygame.init() 598 self._internal_surface = pygame.display.set_mode((self._width, self._height)) 599 self._output_surface = self._internal_surface 600 601 for action in self._internal_pre_show_actions: 602 action() 603 604 self._inner_loop(quit_immediately=quit_immediately) 605 606 def _inner_loop(self, quit_immediately=False): 607 clock = pygame.time.Clock() 608 609 while not self._internal_quit_requested: 610 time_delta = clock.tick(60) / 1000.0 611 612 for event in pygame.event.get(): 613 self._process_event(event) 614 if self._dialog_layer: 615 self._dialog_layer.get_manager().process_events(event) 616 dialog = self._dialog_layer.get_dialog() 617 try: 618 if dialog is not None and event.ui_element == dialog: 619 self._dialog_layer.report_close(event) 620 except AttributeError: 621 pass 622 623 if self._dialog_layer: 624 self._dialog_layer.get_manager().update(time_delta) 625 626 if self._callback_step is not None: 627 self._callback_step(self) 628 629 if self._dialog_layer: 630 self._dialog_layer.get_manager().draw_ui(self._internal_surface) 631 632 pygame.display.update() 633 self._internal_clock.tick(self._state_frame_rate) 634 635 if quit_immediately: 636 self._internal_quit_requested = True 637 638 if self._callback_quit is not None: 639 self._callback_quit(self) 640 641 def _process_event(self, event): 642 if event.type not in self._struct_event_handlers: 643 return 644 645 self._struct_event_handlers[event.type](event) 646 647 def _process_quit(self, event): 648 self._internal_quit_requested = True 649 650 def _process_mouse_down(self, event): 651 self._mouse.report_mouse_down(event) 652 653 def _process_mouse_up(self, event): 654 self._mouse.report_mouse_up(event) 655 656 def _process_key_down(self, event): 657 self._keyboard.report_key_down(event) 658 659 def _process_key_up(self, event): 660 self._keyboard.report_key_up(event) 661 662 def _create_state_machine(self) -> sketchingpy.state_struct.SketchStateMachine: 663 return PygameSketchStateMachine() 664 665 def _make_shape_surface(self, rect: pygame.Rect, stroke_weight: float, 666 has_alpha: bool = True) -> pygame.Surface: 667 if has_alpha: 668 return pygame.Surface((rect.w + stroke_weight, rect.h + stroke_weight), pygame.SRCALPHA) 669 else: 670 return pygame.Surface((rect.w + stroke_weight, rect.h + stroke_weight)) 671 672 def _zero_rect(self, rect: pygame.Rect) -> pygame.Rect: 673 return pygame.Rect(0, 0, rect.w, rect.h) 674 675 def _build_rect_with_mode(self, x1: float, y1: float, x2: float, y2: float, 676 native_mode: int) -> pygame.Rect: 677 if native_mode == sketchingpy.const.CENTER: 678 start_x = x1 - math.floor(x2 / 2) 679 start_y = y1 - math.floor(y2 / 2) 680 width = x2 681 height = y2 682 elif native_mode == sketchingpy.const.RADIUS: 683 start_x = x1 - x2 684 start_y = y1 - y2 685 width = x2 * 2 686 height = y2 * 2 687 elif native_mode == sketchingpy.const.CORNER: 688 start_x = x1 689 start_y = y1 690 width = x2 691 height = y2 692 elif native_mode == sketchingpy.const.CORNERS: 693 (x1, y1, x2, y2) = sketchingpy.abstracted.reorder_coords(x1, y1, x2, y2) 694 start_x = x1 695 start_y = y1 696 width = x2 - x1 697 height = y2 - y1 698 else: 699 raise RuntimeError('Unknown mode: ' + str(native_mode)) 700 701 return pygame.Rect(start_x, start_y, width, height) 702 703 def _draw_primitive(self, x1: float, y1: float, x2: float, y2: float, 704 mode: str, native_mode, draw_method): 705 state_machine = self._get_current_state_machine() 706 has_fill = state_machine.get_fill_enabled() 707 fill_color = state_machine.get_fill_native() 708 has_stroke = state_machine.get_stroke_enabled() 709 stroke_color = state_machine.get_stroke_native() 710 rect = self._build_rect_with_mode(x1, y1, x2, y2, native_mode) 711 stroke_weight = state_machine.get_stroke_weight_native() 712 713 transformer = self._transformer.quick_copy() 714 715 def execute_draw_piece(color, strategy): 716 target_surface = self._make_shape_surface(rect, stroke_weight) 717 rect_adj = self._zero_rect(rect) 718 719 strategy(target_surface, rect_adj) 720 721 self._blit_with_transform( 722 target_surface, 723 rect.centerx, 724 rect.centery, 725 transformer 726 ) 727 728 def execute_draw(): 729 if has_fill: 730 execute_draw_piece( 731 fill_color, 732 lambda surface, rect: draw_method( 733 surface, 734 fill_color, 735 self._offset_fill_weight(rect, stroke_weight), 736 0 737 ) 738 ) 739 740 if has_stroke: 741 execute_draw_piece( 742 stroke_color, 743 lambda surface, rect: draw_method( 744 surface, 745 stroke_color, 746 self._offset_stroke_weight(rect, stroke_weight), 747 stroke_weight 748 ) 749 ) 750 751 if self._internal_surface is None: 752 self._internal_pre_show_actions.append(execute_draw) 753 return 754 else: 755 execute_draw() 756 757 def _to_pillow_rgba(self, target: pygame.Color): 758 return (target.r, target.g, target.b, target.a) 759 760 def _convert_pillow_image(self, target: PIL.Image.Image) -> pygame.Surface: 761 return pygame.image.fromstring( 762 target.tobytes(), 763 target.size, 764 target.mode # type: ignore 765 ).convert_alpha() 766 767 def _blit_with_transform(self, surface: pygame.Surface, x: float, y: float, 768 transformer: sketchingpy.transform.Transformer): 769 start_rect = surface.get_rect() 770 start_rect.centerx = x # type: ignore 771 start_rect.centery = y # type: ignore 772 773 transformed_center = transformer.transform( 774 start_rect.centerx, 775 start_rect.centery 776 ) 777 778 has_scale = transformed_center.get_scale() != 1 779 has_rotation = transformed_center.get_rotation() != 0 780 has_content_transform = has_scale or has_rotation 781 if has_content_transform: 782 angle = transformed_center.get_rotation() 783 angle_transform = math.degrees(angle) 784 scale = transformed_center.get_scale() 785 surface = pygame.transform.rotozoom(surface, angle_transform, scale) 786 end_rect = surface.get_rect() 787 else: 788 end_rect = start_rect 789 790 end_rect.centerx = transformed_center.get_x() # type: ignore 791 end_rect.centery = transformed_center.get_y() # type: ignore 792 793 assert self._internal_surface is not None 794 self._internal_surface.blit(surface, (end_rect.x, end_rect.y)) 795 796 797class PygameSketchStateMachine(sketchingpy.state_struct.SketchStateMachine): 798 """Implementation of SketchStateMachine for Pygame types.""" 799 800 def __init__(self): 801 """Create a new state machine for Pygame-based sketches.""" 802 super().__init__() 803 self._fill_native = pygame.Color(super().get_fill()) 804 self._stroke_native = pygame.Color(super().get_stroke()) 805 self._font_cache = {} 806 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 807 808 def set_fill(self, fill: str): 809 super().set_fill(fill) 810 self._fill_native = pygame.Color(super().get_fill()) 811 812 def get_fill_native(self): 813 return self._fill_native 814 815 def set_stroke(self, stroke: str): 816 super().set_stroke(stroke) 817 self._stroke_native = pygame.Color(super().get_stroke()) 818 819 def get_stroke_native(self): 820 return self._stroke_native 821 822 def get_text_font_native(self): 823 font = self.get_text_font() 824 key = '%s.%d' % (font.get_identifier(), font.get_size()) 825 826 if key not in self._font_cache: 827 new_font = PIL.ImageFont.truetype(font.get_identifier(), font.get_size()) 828 self._font_cache[key] = new_font 829 830 return self._font_cache[key] 831 832 def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign): 833 super().set_text_align(text_align) 834 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 835 836 def get_text_align_native(self): 837 return self._text_align_native 838 839 def _transform_text_align(self, 840 text_align: sketchingpy.state_struct.TextAlign) -> sketchingpy.state_struct.TextAlign: 841 842 HORIZONTAL_ALIGNS = { 843 sketchingpy.const.LEFT: 'l', 844 sketchingpy.const.CENTER: 'm', 845 sketchingpy.const.RIGHT: 'r' 846 } 847 848 VERTICAL_ALIGNS = { 849 sketchingpy.const.TOP: 't', 850 sketchingpy.const.CENTER: 'm', 851 sketchingpy.const.BASELINE: 's', 852 sketchingpy.const.BOTTOM: 'b' 853 } 854 855 return sketchingpy.state_struct.TextAlign( 856 HORIZONTAL_ALIGNS[text_align.get_horizontal_align()], 857 VERTICAL_ALIGNS[text_align.get_vertical_align()] 858 ) 859 860 861class PygameImage(sketchingpy.abstracted.Image): 862 """Strategy implementation for Pygame images.""" 863 864 def __init__(self, src: str): 865 """Create a new image. 866 867 Args: 868 src: Path to the image. 869 """ 870 super().__init__(src) 871 self._native = pygame.image.load(self.get_src()) 872 self._converted = False 873 874 def get_width(self) -> float: 875 return self._native.get_rect().width 876 877 def get_height(self) -> float: 878 return self._native.get_rect().height 879 880 def resize(self, width: float, height: float): 881 self._native = pygame.transform.scale(self._native, (width, height)) 882 883 def get_native(self): 884 if not self._converted: 885 self._native.convert_alpha() 886 887 return self._native 888 889 def get_is_loaded(self): 890 return True 891 892 893class PygameMouse(sketchingpy.control_struct.Mouse): 894 """Strategy implementation for Pygame-based mouse access.""" 895 896 def __init__(self): 897 """Create a new mouse strategy using Pygame.""" 898 super().__init__() 899 self._press_callback = None 900 self._release_callback = None 901 902 def get_pointer_x(self): 903 return pygame.mouse.get_pos()[0] 904 905 def get_pointer_y(self): 906 return pygame.mouse.get_pos()[1] 907 908 def get_buttons_pressed(self) -> sketchingpy.control_struct.Buttons: 909 is_left_pressed = pygame.mouse.get_pressed()[0] 910 is_right_pressed = pygame.mouse.get_pressed()[2] 911 buttons_clicked = [] 912 913 if is_left_pressed: 914 buttons_clicked.append(sketchingpy.const.MOUSE_LEFT_BUTTON) 915 916 if is_right_pressed: 917 buttons_clicked.append(sketchingpy.const.MOUSE_RIGHT_BUTTON) 918 919 return map(lambda x: sketchingpy.control_struct.Button(x), buttons_clicked) 920 921 def on_button_press(self, callback: sketchingpy.control_struct.MouseCallback): 922 self._press_callback = callback 923 924 def on_button_release(self, callback: sketchingpy.control_struct.MouseCallback): 925 self._release_callback = callback 926 927 def report_mouse_down(self, event): 928 if self._press_callback is None: 929 return 930 931 if event.button == 1: 932 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_LEFT_BUTTON) 933 self._press_callback(button) 934 elif event.button == 3: 935 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_RIGHT_BUTTON) 936 self._press_callback(button) 937 938 def report_mouse_up(self, event): 939 if self._release_callback is None: 940 return 941 942 if event.button == 1: 943 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_LEFT_BUTTON) 944 self._release_callback(button) 945 elif event.button == 3: 946 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_RIGHT_BUTTON) 947 self._release_callback(button) 948 949 950class PygameKeyboard(sketchingpy.control_struct.Keyboard): 951 """Strategy implementation for Pygame-based keyboard access.""" 952 953 def __init__(self): 954 """Create a new keyboard strategy using Pygame.""" 955 super().__init__() 956 self._pressed = set() 957 self._press_callback = None 958 self._release_callback = None 959 960 def get_keys_pressed(self) -> sketchingpy.control_struct.Buttons: 961 return map(lambda x: sketchingpy.control_struct.Button(x), self._pressed) 962 963 def on_key_press(self, callback: sketchingpy.control_struct.KeyboardCallback): 964 self._press_callback = callback 965 966 def on_key_release(self, callback: sketchingpy.control_struct.KeyboardCallback): 967 self._release_callback = callback 968 969 def report_key_down(self, event): 970 mapped = sketchingpy.sketch2d_keymap.KEY_MAP.get(event.key, None) 971 972 if mapped is None: 973 return 974 975 self._pressed.add(mapped) 976 977 if self._press_callback is not None: 978 button = sketchingpy.control_struct.Button(mapped) 979 self._press_callback(button) 980 981 def report_key_up(self, event): 982 mapped = sketchingpy.sketch2d_keymap.KEY_MAP.get(event.key, None) 983 984 if mapped is None: 985 return 986 987 self._pressed.remove(mapped) 988 989 if self._release_callback is not None: 990 button = sketchingpy.control_struct.Button(mapped) 991 self._release_callback(button) 992 993 994class AppDialogLayer(sketchingpy.dialog_struct.DialogLayer): 995 """Dialog / simple UI layer for local apps.""" 996 997 def __init__(self, sketch: Sketch2DApp): 998 """"Initialize tkinter but hide the root window.""" 999 self._sketch = sketch 1000 self._sketch_size = self._sketch._get_window_size() 1001 self._manager = pygame_gui.UIManager(self._sketch_size) 1002 self._callback = None # type: ignore 1003 self._dialog = None # type: ignore 1004 1005 def get_manager(self): 1006 return self._manager 1007 1008 def get_dialog(self): 1009 return self._dialog 1010 1011 def report_close(self, event): 1012 if self._callback: 1013 self._callback(event) 1014 1015 def show_alert(self, message: str, callback: typing.Optional[typing.Callable[[], None]] = None): 1016 self._dispose_dialog() 1017 self._set_dialog(pygame_gui.windows.UIMessageWindow( 1018 rect=pygame.Rect( 1019 self._sketch_size[0] / 2 - 150, 1020 self._sketch_size[1] / 2 - 150, 1021 300, 1022 300 1023 ), 1024 html_message=message, 1025 manager=self._manager 1026 )) 1027 1028 def outer_callback(event): 1029 if event.type == pygame_gui._constants.UI_BUTTON_PRESSED and callback: 1030 callback() 1031 1032 self._callback = outer_callback # type: ignore 1033 1034 def show_prompt(self, message: str, 1035 callback: typing.Optional[typing.Callable[[str], None]] = None): 1036 self._set_dialog(sketchingpy.pygame_prompt.PygameGuiPrompt( # type: ignore 1037 rect=pygame.Rect( 1038 self._sketch_size[0] / 2 - 150, 1039 self._sketch_size[1] / 2 - 150, 1040 300, 1041 300 1042 ), 1043 action_long_desc=message, 1044 manager=self._manager, 1045 window_title='Prompt' 1046 )) 1047 1048 def outer_callback(event): 1049 if event.type == pygame_gui._constants.UI_CONFIRMATION_DIALOG_CONFIRMED: 1050 callback(str(self._dialog.get_text())) 1051 1052 self._callback = outer_callback # type: ignore 1053 1054 def get_file_save_location(self, 1055 callback: typing.Optional[typing.Callable[[str], None]] = None): 1056 self._dispose_dialog() 1057 self._set_dialog(pygame_gui.windows.ui_file_dialog.UIFileDialog( 1058 rect=pygame.Rect( 1059 self._sketch_size[0] / 2 - 150, 1060 self._sketch_size[1] / 2 - 150, 1061 300, 1062 300 1063 ), 1064 manager=self._manager, 1065 allow_existing_files_only=False, 1066 window_title='Save' 1067 )) 1068 self._callback = self._make_file_dialog_callback(callback) # type: ignore 1069 1070 def get_file_load_location(self, 1071 callback: typing.Optional[typing.Callable[[str], None]] = None): 1072 self._dispose_dialog() 1073 self._set_dialog(pygame_gui.windows.ui_file_dialog.UIFileDialog( 1074 rect=pygame.Rect( 1075 self._sketch_size[0] / 2 - 150, 1076 self._sketch_size[1] / 2 - 150, 1077 300, 1078 300 1079 ), 1080 manager=self._manager, 1081 allow_existing_files_only=False, 1082 window_title='Load' 1083 )) 1084 self._callback = self._make_file_dialog_callback(callback) # type: ignore 1085 1086 def _make_file_dialog_callback(self, inner_callback): 1087 def callback(event): 1088 if event.type == pygame_gui.UI_FILE_DIALOG_PATH_PICKED: 1089 inner_callback(str(self._dialog.current_file_path)) 1090 1091 return callback 1092 1093 def _dispose_dialog(self): 1094 if self._dialog: 1095 self._dialog.kill() 1096 1097 def _set_dialog(self, new_dialog): 1098 self._dialog = new_dialog # type: ignore
50class Sketch2DApp(sketchingpy.abstracted.Sketch): 51 """Create a new Pygame-based Sketch.""" 52 53 def __init__(self, width: int, height: int, title: typing.Optional[str] = None, 54 loading_src: typing.Optional[str] = None): 55 """Create a enw Pygame-based sketch. 56 57 Args: 58 width: The width of the sketch in pixels. This will be used for window width. 59 height: The height of the sketch in pixels. This will be used for window height. 60 title: Starting title for the application. 61 loading_src: ID for loading screen. Ignored, reserved for future use. 62 """ 63 super().__init__() 64 65 # System params 66 self._width = width 67 self._height = height 68 69 # Callbacks 70 self._callback_step: OPTIONAL_SKETCH_CALLBACK = None 71 self._callback_quit: OPTIONAL_SKETCH_CALLBACK = None 72 73 # User configurable state 74 self._state_frame_rate = DEFAULT_FPS 75 76 # Buffers 77 self._internal_surface = None 78 self._output_surface = None 79 self._buffers: typing.Dict[str, pygame.Surface] = {} 80 81 # Internal state 82 self._internal_pre_show_actions: typing.List[typing.Callable] = [] 83 self._internal_quit_requested = False 84 self._internal_clock = pygame.time.Clock() 85 self._transformer = sketchingpy.transform.Transformer() 86 self._transformer_stack: typing.List[sketchingpy.transform.Transformer] = [] 87 self._dialog_layer: typing.Optional['AppDialogLayer'] = None 88 89 # Inputs 90 self._mouse = PygameMouse() 91 self._keyboard = PygameKeyboard() 92 93 # Internal struct 94 self._struct_event_handlers = { 95 pygame.KEYDOWN: lambda x: self._process_key_down(x), 96 pygame.KEYUP: lambda x: self._process_key_up(x), 97 pygame.MOUSEBUTTONDOWN: lambda x: self._process_mouse_down(x), 98 pygame.MOUSEBUTTONUP: lambda x: self._process_mouse_up(x), 99 pygame.locals.QUIT: lambda x: self._process_quit(x) 100 } 101 102 # Default window properties 103 self.set_title('Sketchingpy Sketch' if title is None else title) 104 105 ########## 106 # Buffer # 107 ########## 108 109 def create_buffer(self, name: str, width: int, height: int, 110 background: typing.Optional[str] = None): 111 def execute(): 112 has_alpha = self._get_is_color_transparent(background) 113 self._buffers[name] = self._make_shape_surface( 114 pygame.Rect(0, 0, width, height), 115 0, 116 has_alpha=has_alpha 117 ) 118 if not has_alpha: 119 self._buffers[name].fill(pygame.Color(background)) 120 121 if self._internal_surface is None: 122 self._internal_pre_show_actions.append(execute) 123 else: 124 execute() 125 126 def enter_buffer(self, name: str): 127 def execute(): 128 self._internal_surface = self._buffers[name] 129 130 if self._internal_surface is None: 131 self._internal_pre_show_actions.append(execute) 132 else: 133 execute() 134 135 def exit_buffer(self): 136 def execute(): 137 self._internal_surface = self._output_surface 138 139 if self._internal_surface is None: 140 self._internal_pre_show_actions.append(execute) 141 else: 142 execute() 143 144 def draw_buffer(self, x: float, y: float, name: str): 145 def execute(): 146 target_surface = self._buffers[name] 147 148 original_rect = target_surface.get_rect() 149 rect = pygame.Rect( 150 original_rect.x, 151 original_rect.y, 152 original_rect.width, 153 original_rect.height 154 ) 155 rect.left = x 156 rect.top = y 157 158 self._blit_with_transform( 159 target_surface, 160 rect.centerx, 161 rect.centery, 162 self._transformer.quick_copy() 163 ) 164 165 if self._internal_surface is None: 166 self._internal_pre_show_actions.append(execute) 167 else: 168 execute() 169 170 ############ 171 # Controls # 172 ############ 173 174 def get_keyboard(self) -> typing.Optional[sketchingpy.control_struct.Keyboard]: 175 return self._keyboard 176 177 def get_mouse(self) -> typing.Optional[sketchingpy.control_struct.Mouse]: 178 return self._mouse 179 180 ######## 181 # Data # 182 ######## 183 184 def get_data_layer(self) -> typing.Optional[sketchingpy.data_struct.DataLayer]: 185 return sketchingpy.local_data_struct.LocalDataLayer() 186 187 ########### 188 # Dialogs # 189 ########### 190 191 def get_dialog_layer(self) -> typing.Optional[sketchingpy.dialog_struct.DialogLayer]: 192 if not ui_available: 193 return None 194 195 if self._dialog_layer is None: 196 self._dialog_layer = AppDialogLayer(self) 197 198 return self._dialog_layer 199 200 ########### 201 # Drawing # 202 ########### 203 204 def clear(self, color_hex: str): 205 if self._internal_surface is None: 206 self._internal_pre_show_actions.append(lambda: self.clear(color_hex)) 207 return 208 209 self._internal_surface.fill(pygame.Color(color_hex)) 210 211 def draw_arc(self, x1: float, y1: float, x2: float, y2: float, a1: float, 212 a2: float): 213 state_machine = self._get_current_state_machine() 214 215 stroke_enabled = state_machine.get_stroke_enabled() 216 fill_enabled = state_machine.get_fill_enabled() 217 stroke_native = state_machine.get_stroke_native() 218 fill_native = state_machine.get_fill_native() 219 stroke_weight = state_machine.get_stroke_weight() 220 221 mode_native = state_machine.get_arc_mode_native() 222 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 223 224 a1_rad = self._convert_to_radians(a1) 225 a2_rad = self._convert_to_radians(a2) 226 227 transformer = self._transformer.quick_copy() 228 229 def execute_draw(): 230 pillow_util_image = sketchingpy.pillow_util.make_arc_image( 231 rect.x, 232 rect.y, 233 rect.w, 234 rect.h, 235 a1_rad, 236 a2_rad, 237 stroke_enabled, 238 fill_enabled, 239 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 240 self._to_pillow_rgba(fill_native) if fill_enabled else None, 241 stroke_weight 242 ) 243 244 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 245 246 self._blit_with_transform( 247 native_image, 248 rect.centerx, 249 rect.centery, 250 transformer 251 ) 252 253 if self._internal_surface is None: 254 self._internal_pre_show_actions.append(execute_draw) 255 else: 256 execute_draw() 257 258 def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float): 259 state_machine = self._get_current_state_machine() 260 261 stroke_enabled = state_machine.get_stroke_enabled() 262 fill_enabled = state_machine.get_fill_enabled() 263 stroke_native = state_machine.get_stroke_native() 264 fill_native = state_machine.get_fill_native() 265 stroke_weight = state_machine.get_stroke_weight() 266 267 mode_native = state_machine.get_ellipse_mode_native() 268 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 269 270 transformer = self._transformer.quick_copy() 271 272 def execute_draw(): 273 pillow_util_image = sketchingpy.pillow_util.make_ellipse_image( 274 rect.x, 275 rect.y, 276 rect.w, 277 rect.h, 278 stroke_enabled, 279 fill_enabled, 280 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 281 self._to_pillow_rgba(fill_native) if fill_enabled else None, 282 stroke_weight 283 ) 284 285 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 286 287 self._blit_with_transform( 288 native_image, 289 rect.centerx, 290 rect.centery, 291 transformer 292 ) 293 294 if self._internal_surface is None: 295 self._internal_pre_show_actions.append(execute_draw) 296 else: 297 execute_draw() 298 299 def draw_line(self, x1: float, y1: float, x2: float, y2: float): 300 state_machine = self._get_current_state_machine() 301 if not state_machine.get_stroke_enabled(): 302 return 303 304 stroke_color = state_machine.get_stroke_native() 305 stroke_weight = state_machine.get_stroke_weight_native() 306 307 transformer = self._transformer.quick_copy() 308 309 def execute_draw(): 310 min_x = min([x1, x2]) 311 max_x = max([x1, x2]) 312 width = max_x - min_x + 2 * stroke_weight 313 314 min_y = min([y1, y2]) 315 max_y = max([y1, y2]) 316 height = max_y - min_y + 2 * stroke_weight 317 318 rect = pygame.Rect(0, 0, width, height) 319 target_surface = self._make_shape_surface(rect, stroke_weight) 320 321 def adjust(target): 322 return ( 323 target[0] - min_x + stroke_weight - 1, 324 target[1] - min_y + stroke_weight - 1, 325 ) 326 327 pygame.draw.line( 328 target_surface, 329 stroke_color, 330 adjust((x1, y1)), 331 adjust((x2, y2)), 332 width=stroke_weight 333 ) 334 335 center_x = (max_x + min_x) / 2 336 center_y = (max_y + min_y) / 2 337 self._blit_with_transform(target_surface, center_x, center_y, transformer) 338 339 if self._internal_surface is None: 340 self._internal_pre_show_actions.append(execute_draw) 341 else: 342 execute_draw() 343 344 def draw_rect(self, x1: float, y1: float, x2: float, y2: float): 345 state_machine = self._get_current_state_machine() 346 347 stroke_enabled = state_machine.get_stroke_enabled() 348 fill_enabled = state_machine.get_fill_enabled() 349 stroke_native = state_machine.get_stroke_native() 350 fill_native = state_machine.get_fill_native() 351 stroke_weight = state_machine.get_stroke_weight() 352 353 mode_native = state_machine.get_rect_mode_native() 354 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 355 356 transformer = self._transformer.quick_copy() 357 358 def execute_draw(): 359 pillow_util_image = sketchingpy.pillow_util.make_rect_image( 360 rect.x, 361 rect.y, 362 rect.w, 363 rect.h, 364 stroke_enabled, 365 fill_enabled, 366 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 367 self._to_pillow_rgba(fill_native) if fill_enabled else None, 368 stroke_weight 369 ) 370 371 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 372 373 self._blit_with_transform( 374 native_image, 375 rect.centerx, 376 rect.centery, 377 transformer 378 ) 379 380 if self._internal_surface is None: 381 self._internal_pre_show_actions.append(execute_draw) 382 else: 383 execute_draw() 384 385 def draw_shape(self, shape: sketchingpy.shape_struct.Shape): 386 if not shape.get_is_finished(): 387 raise RuntimeError('Finish your shape before drawing.') 388 389 state_machine = self._get_current_state_machine() 390 391 stroke_enabled = state_machine.get_stroke_enabled() 392 fill_enabled = state_machine.get_fill_enabled() 393 stroke_native = state_machine.get_stroke_native() 394 fill_native = state_machine.get_fill_native() 395 stroke_weight = state_machine.get_stroke_weight() 396 397 transformer = self._transformer.quick_copy() 398 399 def execute_draw(): 400 pillow_util_image = sketchingpy.pillow_util.make_shape_image( 401 shape, 402 stroke_enabled, 403 fill_enabled, 404 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 405 self._to_pillow_rgba(fill_native) if fill_enabled else None, 406 stroke_weight 407 ) 408 409 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 410 411 min_x = shape.get_min_x() 412 max_x = shape.get_max_x() 413 center_x = (max_x + min_x) / 2 414 415 min_y = shape.get_min_y() 416 max_y = shape.get_max_y() 417 center_y = (max_y + min_y) / 2 418 419 self._blit_with_transform( 420 native_image, 421 center_x, 422 center_y, 423 transformer 424 ) 425 426 if self._internal_surface is None: 427 self._internal_pre_show_actions.append(execute_draw) 428 else: 429 execute_draw() 430 431 def draw_text(self, x: float, y: float, content: str): 432 content = str(content) 433 state_machine = self._get_current_state_machine() 434 435 stroke_enabled = state_machine.get_stroke_enabled() 436 fill_enabled = state_machine.get_fill_enabled() 437 stroke_native = state_machine.get_stroke_native() 438 fill_native = state_machine.get_fill_native() 439 stroke_weight = state_machine.get_stroke_weight() 440 441 text_font = state_machine.get_text_font_native() 442 fill_pillow = self._to_pillow_rgba(fill_native) 443 stroke_pillow = self._to_pillow_rgba(stroke_native) 444 445 align_info = state_machine.get_text_align_native() 446 anchor_str = align_info.get_horizontal_align() + align_info.get_vertical_align() 447 448 transformer = self._transformer.quick_copy() 449 450 def execute_draw(): 451 pillow_util_image = sketchingpy.pillow_util.make_text_image( 452 x, 453 y, 454 content, 455 text_font, 456 stroke_enabled, 457 fill_enabled, 458 stroke_pillow, 459 fill_pillow, 460 stroke_weight, 461 anchor_str 462 ) 463 464 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 465 466 self._blit_with_transform( 467 native_image, 468 pillow_util_image.get_x() + pillow_util_image.get_width() / 2, 469 pillow_util_image.get_y() + pillow_util_image.get_height() / 2, 470 transformer 471 ) 472 473 if self._internal_surface is None: 474 self._internal_pre_show_actions.append(execute_draw) 475 else: 476 execute_draw() 477 478 ########## 479 # Events # 480 ########## 481 482 def on_step(self, callback: sketchingpy.abstracted.StepCallback): 483 self._callback_step = callback 484 485 def on_quit(self, callback: sketchingpy.abstracted.QuitCallback): 486 self._callback_quit = callback 487 488 ######### 489 # Image # 490 ######### 491 492 def get_image(self, src: str) -> sketchingpy.abstracted.Image: 493 return PygameImage(src) 494 495 def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image): 496 if not image.get_is_loaded(): 497 return 498 499 transformer = self._transformer.quick_copy() 500 501 image_mode_native = self._get_current_state_machine().get_image_mode_native() 502 503 def execute_draw(): 504 rect = self._build_rect_with_mode( 505 x, 506 y, 507 image.get_width(), 508 image.get_height(), 509 image_mode_native 510 ) 511 512 surface = image.get_native() 513 self._blit_with_transform(surface, rect.centerx, rect.centery, transformer) 514 515 if self._internal_surface is None: 516 self._internal_pre_show_actions.append(execute_draw) 517 else: 518 execute_draw() 519 520 def save_image(self, path: str): 521 def execute_save(): 522 pygame.image.save(self._internal_surface, path) 523 524 if self._internal_surface is None: 525 self._internal_pre_show_actions.append(execute_save) 526 self.show_and_quit() 527 else: 528 execute_save() 529 530 ######### 531 # State # 532 ######### 533 534 def push_transform(self): 535 self._transformer_stack.append(copy.deepcopy(self._transformer)) 536 537 def pop_transform(self): 538 if len(self._transformer_stack) == 0: 539 raise RuntimeError('Transformation stack empty.') 540 541 self._transformer = self._transformer_stack.pop() 542 543 ########## 544 # System # 545 ########## 546 547 def get_native(self): 548 if self._internal_surface is None: 549 raise RuntimeError('Need to show sketch first before surface is available.') 550 551 return self._internal_surface 552 553 def set_fps(self, rate: int): 554 self._state_frame_rate = rate 555 556 def set_title(self, title: str): 557 def execute(): 558 pygame.display.set_caption(title) 559 560 if self._internal_surface is None: 561 self._internal_pre_show_actions.append(execute) 562 else: 563 execute() 564 565 def quit(self): 566 self._internal_quit_requested = True 567 568 def show(self, ax=None): 569 self._show_internal(ax=ax, quit_immediately=False) 570 571 def show_and_quit(self, ax=None): 572 self._show_internal(ax=ax, quit_immediately=True) 573 574 ############# 575 # Transform # 576 ############# 577 578 def translate(self, x: float, y: float): 579 self._transformer.translate(x, y) 580 581 def rotate(self, angle_mirror: float): 582 angle = -1 * angle_mirror 583 angle_rad = self._convert_to_radians(angle) 584 self._transformer.rotate(angle_rad) 585 586 def scale(self, scale: float): 587 self._transformer.scale(scale) 588 589 ########### 590 # Support # 591 ########### 592 593 def _get_window_size(self) -> typing.Tuple[int, int]: 594 return (self._width, self._height) 595 596 def _show_internal(self, ax=None, quit_immediately=False): 597 self._snapshot_time() 598 pygame.init() 599 self._internal_surface = pygame.display.set_mode((self._width, self._height)) 600 self._output_surface = self._internal_surface 601 602 for action in self._internal_pre_show_actions: 603 action() 604 605 self._inner_loop(quit_immediately=quit_immediately) 606 607 def _inner_loop(self, quit_immediately=False): 608 clock = pygame.time.Clock() 609 610 while not self._internal_quit_requested: 611 time_delta = clock.tick(60) / 1000.0 612 613 for event in pygame.event.get(): 614 self._process_event(event) 615 if self._dialog_layer: 616 self._dialog_layer.get_manager().process_events(event) 617 dialog = self._dialog_layer.get_dialog() 618 try: 619 if dialog is not None and event.ui_element == dialog: 620 self._dialog_layer.report_close(event) 621 except AttributeError: 622 pass 623 624 if self._dialog_layer: 625 self._dialog_layer.get_manager().update(time_delta) 626 627 if self._callback_step is not None: 628 self._callback_step(self) 629 630 if self._dialog_layer: 631 self._dialog_layer.get_manager().draw_ui(self._internal_surface) 632 633 pygame.display.update() 634 self._internal_clock.tick(self._state_frame_rate) 635 636 if quit_immediately: 637 self._internal_quit_requested = True 638 639 if self._callback_quit is not None: 640 self._callback_quit(self) 641 642 def _process_event(self, event): 643 if event.type not in self._struct_event_handlers: 644 return 645 646 self._struct_event_handlers[event.type](event) 647 648 def _process_quit(self, event): 649 self._internal_quit_requested = True 650 651 def _process_mouse_down(self, event): 652 self._mouse.report_mouse_down(event) 653 654 def _process_mouse_up(self, event): 655 self._mouse.report_mouse_up(event) 656 657 def _process_key_down(self, event): 658 self._keyboard.report_key_down(event) 659 660 def _process_key_up(self, event): 661 self._keyboard.report_key_up(event) 662 663 def _create_state_machine(self) -> sketchingpy.state_struct.SketchStateMachine: 664 return PygameSketchStateMachine() 665 666 def _make_shape_surface(self, rect: pygame.Rect, stroke_weight: float, 667 has_alpha: bool = True) -> pygame.Surface: 668 if has_alpha: 669 return pygame.Surface((rect.w + stroke_weight, rect.h + stroke_weight), pygame.SRCALPHA) 670 else: 671 return pygame.Surface((rect.w + stroke_weight, rect.h + stroke_weight)) 672 673 def _zero_rect(self, rect: pygame.Rect) -> pygame.Rect: 674 return pygame.Rect(0, 0, rect.w, rect.h) 675 676 def _build_rect_with_mode(self, x1: float, y1: float, x2: float, y2: float, 677 native_mode: int) -> pygame.Rect: 678 if native_mode == sketchingpy.const.CENTER: 679 start_x = x1 - math.floor(x2 / 2) 680 start_y = y1 - math.floor(y2 / 2) 681 width = x2 682 height = y2 683 elif native_mode == sketchingpy.const.RADIUS: 684 start_x = x1 - x2 685 start_y = y1 - y2 686 width = x2 * 2 687 height = y2 * 2 688 elif native_mode == sketchingpy.const.CORNER: 689 start_x = x1 690 start_y = y1 691 width = x2 692 height = y2 693 elif native_mode == sketchingpy.const.CORNERS: 694 (x1, y1, x2, y2) = sketchingpy.abstracted.reorder_coords(x1, y1, x2, y2) 695 start_x = x1 696 start_y = y1 697 width = x2 - x1 698 height = y2 - y1 699 else: 700 raise RuntimeError('Unknown mode: ' + str(native_mode)) 701 702 return pygame.Rect(start_x, start_y, width, height) 703 704 def _draw_primitive(self, x1: float, y1: float, x2: float, y2: float, 705 mode: str, native_mode, draw_method): 706 state_machine = self._get_current_state_machine() 707 has_fill = state_machine.get_fill_enabled() 708 fill_color = state_machine.get_fill_native() 709 has_stroke = state_machine.get_stroke_enabled() 710 stroke_color = state_machine.get_stroke_native() 711 rect = self._build_rect_with_mode(x1, y1, x2, y2, native_mode) 712 stroke_weight = state_machine.get_stroke_weight_native() 713 714 transformer = self._transformer.quick_copy() 715 716 def execute_draw_piece(color, strategy): 717 target_surface = self._make_shape_surface(rect, stroke_weight) 718 rect_adj = self._zero_rect(rect) 719 720 strategy(target_surface, rect_adj) 721 722 self._blit_with_transform( 723 target_surface, 724 rect.centerx, 725 rect.centery, 726 transformer 727 ) 728 729 def execute_draw(): 730 if has_fill: 731 execute_draw_piece( 732 fill_color, 733 lambda surface, rect: draw_method( 734 surface, 735 fill_color, 736 self._offset_fill_weight(rect, stroke_weight), 737 0 738 ) 739 ) 740 741 if has_stroke: 742 execute_draw_piece( 743 stroke_color, 744 lambda surface, rect: draw_method( 745 surface, 746 stroke_color, 747 self._offset_stroke_weight(rect, stroke_weight), 748 stroke_weight 749 ) 750 ) 751 752 if self._internal_surface is None: 753 self._internal_pre_show_actions.append(execute_draw) 754 return 755 else: 756 execute_draw() 757 758 def _to_pillow_rgba(self, target: pygame.Color): 759 return (target.r, target.g, target.b, target.a) 760 761 def _convert_pillow_image(self, target: PIL.Image.Image) -> pygame.Surface: 762 return pygame.image.fromstring( 763 target.tobytes(), 764 target.size, 765 target.mode # type: ignore 766 ).convert_alpha() 767 768 def _blit_with_transform(self, surface: pygame.Surface, x: float, y: float, 769 transformer: sketchingpy.transform.Transformer): 770 start_rect = surface.get_rect() 771 start_rect.centerx = x # type: ignore 772 start_rect.centery = y # type: ignore 773 774 transformed_center = transformer.transform( 775 start_rect.centerx, 776 start_rect.centery 777 ) 778 779 has_scale = transformed_center.get_scale() != 1 780 has_rotation = transformed_center.get_rotation() != 0 781 has_content_transform = has_scale or has_rotation 782 if has_content_transform: 783 angle = transformed_center.get_rotation() 784 angle_transform = math.degrees(angle) 785 scale = transformed_center.get_scale() 786 surface = pygame.transform.rotozoom(surface, angle_transform, scale) 787 end_rect = surface.get_rect() 788 else: 789 end_rect = start_rect 790 791 end_rect.centerx = transformed_center.get_x() # type: ignore 792 end_rect.centery = transformed_center.get_y() # type: ignore 793 794 assert self._internal_surface is not None 795 self._internal_surface.blit(surface, (end_rect.x, end_rect.y))
Create a new Pygame-based Sketch.
53 def __init__(self, width: int, height: int, title: typing.Optional[str] = None, 54 loading_src: typing.Optional[str] = None): 55 """Create a enw Pygame-based sketch. 56 57 Args: 58 width: The width of the sketch in pixels. This will be used for window width. 59 height: The height of the sketch in pixels. This will be used for window height. 60 title: Starting title for the application. 61 loading_src: ID for loading screen. Ignored, reserved for future use. 62 """ 63 super().__init__() 64 65 # System params 66 self._width = width 67 self._height = height 68 69 # Callbacks 70 self._callback_step: OPTIONAL_SKETCH_CALLBACK = None 71 self._callback_quit: OPTIONAL_SKETCH_CALLBACK = None 72 73 # User configurable state 74 self._state_frame_rate = DEFAULT_FPS 75 76 # Buffers 77 self._internal_surface = None 78 self._output_surface = None 79 self._buffers: typing.Dict[str, pygame.Surface] = {} 80 81 # Internal state 82 self._internal_pre_show_actions: typing.List[typing.Callable] = [] 83 self._internal_quit_requested = False 84 self._internal_clock = pygame.time.Clock() 85 self._transformer = sketchingpy.transform.Transformer() 86 self._transformer_stack: typing.List[sketchingpy.transform.Transformer] = [] 87 self._dialog_layer: typing.Optional['AppDialogLayer'] = None 88 89 # Inputs 90 self._mouse = PygameMouse() 91 self._keyboard = PygameKeyboard() 92 93 # Internal struct 94 self._struct_event_handlers = { 95 pygame.KEYDOWN: lambda x: self._process_key_down(x), 96 pygame.KEYUP: lambda x: self._process_key_up(x), 97 pygame.MOUSEBUTTONDOWN: lambda x: self._process_mouse_down(x), 98 pygame.MOUSEBUTTONUP: lambda x: self._process_mouse_up(x), 99 pygame.locals.QUIT: lambda x: self._process_quit(x) 100 } 101 102 # Default window properties 103 self.set_title('Sketchingpy Sketch' if title is None else title)
Create a enw Pygame-based sketch.
Arguments:
- width: The width of the sketch in pixels. This will be used for window width.
- height: The height of the sketch in pixels. This will be used for window height.
- title: Starting title for the application.
- loading_src: ID for loading screen. Ignored, reserved for future use.
109 def create_buffer(self, name: str, width: int, height: int, 110 background: typing.Optional[str] = None): 111 def execute(): 112 has_alpha = self._get_is_color_transparent(background) 113 self._buffers[name] = self._make_shape_surface( 114 pygame.Rect(0, 0, width, height), 115 0, 116 has_alpha=has_alpha 117 ) 118 if not has_alpha: 119 self._buffers[name].fill(pygame.Color(background)) 120 121 if self._internal_surface is None: 122 self._internal_pre_show_actions.append(execute) 123 else: 124 execute()
Create a new named in-memory (or equivalent) buffer.
Arguments:
- name: The name of the buffer. If a prior buffer of this name exists, it will be replaced.
- width: The width of the buffer in pixels. In some renderers, the buffer will clip. In others, out of buffer values may be drawn.
- height: The height of the buffer in pixels. In some renderers, the buffer will clip. In others, out of buffer values may be drawn.
- background: The background to use for this buffer or None if transparent. Defaults to None.
126 def enter_buffer(self, name: str): 127 def execute(): 128 self._internal_surface = self._buffers[name] 129 130 if self._internal_surface is None: 131 self._internal_pre_show_actions.append(execute) 132 else: 133 execute()
Switch rendering context to a buffer, exiting current buffer if active.
Arguments:
- name: The name of the buffer to which context should switch.
135 def exit_buffer(self): 136 def execute(): 137 self._internal_surface = self._output_surface 138 139 if self._internal_surface is None: 140 self._internal_pre_show_actions.append(execute) 141 else: 142 execute()
Exit the current offscreen buffer.
Exit the current offscreen buffer, returning to the actual sketch. This will act as a noop if not currently in a buffer.
144 def draw_buffer(self, x: float, y: float, name: str): 145 def execute(): 146 target_surface = self._buffers[name] 147 148 original_rect = target_surface.get_rect() 149 rect = pygame.Rect( 150 original_rect.x, 151 original_rect.y, 152 original_rect.width, 153 original_rect.height 154 ) 155 rect.left = x 156 rect.top = y 157 158 self._blit_with_transform( 159 target_surface, 160 rect.centerx, 161 rect.centery, 162 self._transformer.quick_copy() 163 ) 164 165 if self._internal_surface is None: 166 self._internal_pre_show_actions.append(execute) 167 else: 168 execute()
Draw an offscreen buffer to the current buffer or sketch.
Arguments:
- x: The horizontal position in pixels at which the left should be drawn.
- y: The vertical position in pixels at which the top should be drawn.
- name: The name of the buffer to draw.
174 def get_keyboard(self) -> typing.Optional[sketchingpy.control_struct.Keyboard]: 175 return self._keyboard
Get access to the keyboard.
Get access to the keyboard currently registered with the operating system for the sketch. Different sketches running at the same time may have different keyboards depending on focus or OS configuration.
Returns:
Current keyboard or None if not found / supported.
177 def get_mouse(self) -> typing.Optional[sketchingpy.control_struct.Mouse]: 178 return self._mouse
Get access to the mouse.
Get access to the mouse currently registered with the operating system for the sketch. Different sketches running at the same time may have different mouse objects depending on focus or OS configuration. Note that the mouse may also be emulated if the device uses a touch screen.
Returns:
Current mouse or None if not found / supported.
184 def get_data_layer(self) -> typing.Optional[sketchingpy.data_struct.DataLayer]: 185 return sketchingpy.local_data_struct.LocalDataLayer()
Get access to reading and writing data.
Open access to the file system, network, or browser to read or write data.
Returns:
Facade for data access or None if not supported or insufficient permissions.
191 def get_dialog_layer(self) -> typing.Optional[sketchingpy.dialog_struct.DialogLayer]: 192 if not ui_available: 193 return None 194 195 if self._dialog_layer is None: 196 self._dialog_layer = AppDialogLayer(self) 197 198 return self._dialog_layer
Get access to rendering and using simple dialogs.
Open access to a simple dialog prefabricated UI system to show alerts, prompts, and other dialog boxes.
Returns:
Facade for rendering dialogs or None if not supported or insufficient permissions.
204 def clear(self, color_hex: str): 205 if self._internal_surface is None: 206 self._internal_pre_show_actions.append(lambda: self.clear(color_hex)) 207 return 208 209 self._internal_surface.fill(pygame.Color(color_hex))
Clear the sketch to a color.
Peform the equivalent of drawing a rectangle the size of the sketch without stroke and with the given fill color.
Arguments:
- color: The color to use in clearing.
211 def draw_arc(self, x1: float, y1: float, x2: float, y2: float, a1: float, 212 a2: float): 213 state_machine = self._get_current_state_machine() 214 215 stroke_enabled = state_machine.get_stroke_enabled() 216 fill_enabled = state_machine.get_fill_enabled() 217 stroke_native = state_machine.get_stroke_native() 218 fill_native = state_machine.get_fill_native() 219 stroke_weight = state_machine.get_stroke_weight() 220 221 mode_native = state_machine.get_arc_mode_native() 222 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 223 224 a1_rad = self._convert_to_radians(a1) 225 a2_rad = self._convert_to_radians(a2) 226 227 transformer = self._transformer.quick_copy() 228 229 def execute_draw(): 230 pillow_util_image = sketchingpy.pillow_util.make_arc_image( 231 rect.x, 232 rect.y, 233 rect.w, 234 rect.h, 235 a1_rad, 236 a2_rad, 237 stroke_enabled, 238 fill_enabled, 239 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 240 self._to_pillow_rgba(fill_native) if fill_enabled else None, 241 stroke_weight 242 ) 243 244 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 245 246 self._blit_with_transform( 247 native_image, 248 rect.centerx, 249 rect.centery, 250 transformer 251 ) 252 253 if self._internal_surface is None: 254 self._internal_pre_show_actions.append(execute_draw) 255 else: 256 execute_draw()
Draw a partial ellipse using starting and ending angles.
Using starting and ending angles, draw a partial ellipse which is either drawn outside line only (stroke) and / or filled from the center of that ellipse.
Arguments:
- x1: The x location at which to draw the arc.
- y1: The y location at which to draw the arc.
- x2: Horizontal size.
- y2: Vertical size.
- a1: Starting angle.
- a2: Ending angle.
258 def draw_ellipse(self, x1: float, y1: float, x2: float, y2: float): 259 state_machine = self._get_current_state_machine() 260 261 stroke_enabled = state_machine.get_stroke_enabled() 262 fill_enabled = state_machine.get_fill_enabled() 263 stroke_native = state_machine.get_stroke_native() 264 fill_native = state_machine.get_fill_native() 265 stroke_weight = state_machine.get_stroke_weight() 266 267 mode_native = state_machine.get_ellipse_mode_native() 268 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 269 270 transformer = self._transformer.quick_copy() 271 272 def execute_draw(): 273 pillow_util_image = sketchingpy.pillow_util.make_ellipse_image( 274 rect.x, 275 rect.y, 276 rect.w, 277 rect.h, 278 stroke_enabled, 279 fill_enabled, 280 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 281 self._to_pillow_rgba(fill_native) if fill_enabled else None, 282 stroke_weight 283 ) 284 285 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 286 287 self._blit_with_transform( 288 native_image, 289 rect.centerx, 290 rect.centery, 291 transformer 292 ) 293 294 if self._internal_surface is None: 295 self._internal_pre_show_actions.append(execute_draw) 296 else: 297 execute_draw()
Draw a circle or ellipse.
Draw an ellipse or, in the case of equal width and height, a circle.
Arguments:
- x1: The x location at which to draw the ellipse.
- y1: The y location at which to draw the ellipse.
- x2: Horizontal size.
- y2: Vertical size.
299 def draw_line(self, x1: float, y1: float, x2: float, y2: float): 300 state_machine = self._get_current_state_machine() 301 if not state_machine.get_stroke_enabled(): 302 return 303 304 stroke_color = state_machine.get_stroke_native() 305 stroke_weight = state_machine.get_stroke_weight_native() 306 307 transformer = self._transformer.quick_copy() 308 309 def execute_draw(): 310 min_x = min([x1, x2]) 311 max_x = max([x1, x2]) 312 width = max_x - min_x + 2 * stroke_weight 313 314 min_y = min([y1, y2]) 315 max_y = max([y1, y2]) 316 height = max_y - min_y + 2 * stroke_weight 317 318 rect = pygame.Rect(0, 0, width, height) 319 target_surface = self._make_shape_surface(rect, stroke_weight) 320 321 def adjust(target): 322 return ( 323 target[0] - min_x + stroke_weight - 1, 324 target[1] - min_y + stroke_weight - 1, 325 ) 326 327 pygame.draw.line( 328 target_surface, 329 stroke_color, 330 adjust((x1, y1)), 331 adjust((x2, y2)), 332 width=stroke_weight 333 ) 334 335 center_x = (max_x + min_x) / 2 336 center_y = (max_y + min_y) / 2 337 self._blit_with_transform(target_surface, center_x, center_y, transformer) 338 339 if self._internal_surface is None: 340 self._internal_pre_show_actions.append(execute_draw) 341 else: 342 execute_draw()
Draw a simple line.
Draw a line between two points.
Arguments:
- x1: The x coordinate from which the line should be drawn.
- y1: The y coordinate from which the line should be drawn.
- x2: The x coordinate to which the line should be drawn.
- y2: The y coordinate to which the line should be drawn.
344 def draw_rect(self, x1: float, y1: float, x2: float, y2: float): 345 state_machine = self._get_current_state_machine() 346 347 stroke_enabled = state_machine.get_stroke_enabled() 348 fill_enabled = state_machine.get_fill_enabled() 349 stroke_native = state_machine.get_stroke_native() 350 fill_native = state_machine.get_fill_native() 351 stroke_weight = state_machine.get_stroke_weight() 352 353 mode_native = state_machine.get_rect_mode_native() 354 rect = self._build_rect_with_mode(x1, y1, x2, y2, mode_native) 355 356 transformer = self._transformer.quick_copy() 357 358 def execute_draw(): 359 pillow_util_image = sketchingpy.pillow_util.make_rect_image( 360 rect.x, 361 rect.y, 362 rect.w, 363 rect.h, 364 stroke_enabled, 365 fill_enabled, 366 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 367 self._to_pillow_rgba(fill_native) if fill_enabled else None, 368 stroke_weight 369 ) 370 371 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 372 373 self._blit_with_transform( 374 native_image, 375 rect.centerx, 376 rect.centery, 377 transformer 378 ) 379 380 if self._internal_surface is None: 381 self._internal_pre_show_actions.append(execute_draw) 382 else: 383 execute_draw()
Draw a rectangle.
Draw a rectangle or, if width and height are the same, a square.
Arguments:
- x1: The x location at which to draw the rectangle.
- y1: The y location at which to draw the rectangle.
- x2: Horizontal size.
- y2: Vertical size.
385 def draw_shape(self, shape: sketchingpy.shape_struct.Shape): 386 if not shape.get_is_finished(): 387 raise RuntimeError('Finish your shape before drawing.') 388 389 state_machine = self._get_current_state_machine() 390 391 stroke_enabled = state_machine.get_stroke_enabled() 392 fill_enabled = state_machine.get_fill_enabled() 393 stroke_native = state_machine.get_stroke_native() 394 fill_native = state_machine.get_fill_native() 395 stroke_weight = state_machine.get_stroke_weight() 396 397 transformer = self._transformer.quick_copy() 398 399 def execute_draw(): 400 pillow_util_image = sketchingpy.pillow_util.make_shape_image( 401 shape, 402 stroke_enabled, 403 fill_enabled, 404 self._to_pillow_rgba(stroke_native) if stroke_enabled else None, 405 self._to_pillow_rgba(fill_native) if fill_enabled else None, 406 stroke_weight 407 ) 408 409 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 410 411 min_x = shape.get_min_x() 412 max_x = shape.get_max_x() 413 center_x = (max_x + min_x) / 2 414 415 min_y = shape.get_min_y() 416 max_y = shape.get_max_y() 417 center_y = (max_y + min_y) / 2 418 419 self._blit_with_transform( 420 native_image, 421 center_x, 422 center_y, 423 transformer 424 ) 425 426 if self._internal_surface is None: 427 self._internal_pre_show_actions.append(execute_draw) 428 else: 429 execute_draw()
Draw a shape.
Draw a shape which consists of multiple line or curve segments and which can be either open (stroke only) or closed (can be filled).
Arguments:
- shape: The shape to draw.
431 def draw_text(self, x: float, y: float, content: str): 432 content = str(content) 433 state_machine = self._get_current_state_machine() 434 435 stroke_enabled = state_machine.get_stroke_enabled() 436 fill_enabled = state_machine.get_fill_enabled() 437 stroke_native = state_machine.get_stroke_native() 438 fill_native = state_machine.get_fill_native() 439 stroke_weight = state_machine.get_stroke_weight() 440 441 text_font = state_machine.get_text_font_native() 442 fill_pillow = self._to_pillow_rgba(fill_native) 443 stroke_pillow = self._to_pillow_rgba(stroke_native) 444 445 align_info = state_machine.get_text_align_native() 446 anchor_str = align_info.get_horizontal_align() + align_info.get_vertical_align() 447 448 transformer = self._transformer.quick_copy() 449 450 def execute_draw(): 451 pillow_util_image = sketchingpy.pillow_util.make_text_image( 452 x, 453 y, 454 content, 455 text_font, 456 stroke_enabled, 457 fill_enabled, 458 stroke_pillow, 459 fill_pillow, 460 stroke_weight, 461 anchor_str 462 ) 463 464 native_image = self._convert_pillow_image(pillow_util_image.get_image()) 465 466 self._blit_with_transform( 467 native_image, 468 pillow_util_image.get_x() + pillow_util_image.get_width() / 2, 469 pillow_util_image.get_y() + pillow_util_image.get_height() / 2, 470 transformer 471 ) 472 473 if self._internal_surface is None: 474 self._internal_pre_show_actions.append(execute_draw) 475 else: 476 execute_draw()
Draw text using the current font.
Draw text using the current font and alignment.
Arguments:
- x: The x coordinate at which to draw the text.
- y: The y coordinate at which to draw the text.
- text: The string to draw.
482 def on_step(self, callback: sketchingpy.abstracted.StepCallback): 483 self._callback_step = callback
Callback for when the sketch ends execution.
Register a callback for when the sketch redraws. This function should expect a single parameter which is the sketch redrawing.
Arguments:
- callback: The function to invoke when the sketch stops execution.
485 def on_quit(self, callback: sketchingpy.abstracted.QuitCallback): 486 self._callback_quit = callback
Callback for when the sketch ends execution.
Register a callback for when the sketch terminates.
Arguments:
- callback: The function to invoke when the sketch stops execution.
Load an image file.
Load an image from the local file system or URL.
Arguments:
- src: The location from which the file should be read.
495 def draw_image(self, x: float, y: float, image: sketchingpy.abstracted.Image): 496 if not image.get_is_loaded(): 497 return 498 499 transformer = self._transformer.quick_copy() 500 501 image_mode_native = self._get_current_state_machine().get_image_mode_native() 502 503 def execute_draw(): 504 rect = self._build_rect_with_mode( 505 x, 506 y, 507 image.get_width(), 508 image.get_height(), 509 image_mode_native 510 ) 511 512 surface = image.get_native() 513 self._blit_with_transform(surface, rect.centerx, rect.centery, transformer) 514 515 if self._internal_surface is None: 516 self._internal_pre_show_actions.append(execute_draw) 517 else: 518 execute_draw()
Draw an image at a location.
Draw a previously loaded image at a specific coordinate using its current size.
Arguments:
- x: Horizontal coordinate at which to draw the image.
- y: Vertical coordinate at which to draw the image.
- image: The image to draw.
520 def save_image(self, path: str): 521 def execute_save(): 522 pygame.image.save(self._internal_surface, path) 523 524 if self._internal_surface is None: 525 self._internal_pre_show_actions.append(execute_save) 526 self.show_and_quit() 527 else: 528 execute_save()
Save an image file.
Save the sketch as an image file, either directly to the file system or as a download.
Arguments:
- path: The location at which the file should be written.
Save current transformation state.
Save current sketch transformation state to the matrix history. This works as a stack (like a stack of plates) where this puts a new plate on the top of the pile. This will leave the current transformation matrix in the sketch unchanged.
537 def pop_transform(self): 538 if len(self._transformer_stack) == 0: 539 raise RuntimeError('Transformation stack empty.') 540 541 self._transformer = self._transformer_stack.pop()
Restore a previously saved transformation state.
Restore the most recently transformation configuration saved in matrix history, removing that "transform matrix" from the history. This works as a stack (like a stack of plates) where the top of the pile is taken off and restored, removing it from that stack. This will overwrite the current transformation configuration in the sketch.
547 def get_native(self): 548 if self._internal_surface is None: 549 raise RuntimeError('Need to show sketch first before surface is available.') 550 551 return self._internal_surface
Get a reference to the underlying native renderer object.
Returns:
Native render object.
Indicate how fast the sketch should redraw.
Indicate a target frames per second that the sketch will take a "step" or redraw. Note that this is a goal and, if the system fall behind, it will drop frames and cause the on_step callback to be executed fewer times than the target.
Arguments:
- rate: The number of frames to try to draw per second.
556 def set_title(self, title: str): 557 def execute(): 558 pygame.display.set_caption(title) 559 560 if self._internal_surface is None: 561 self._internal_pre_show_actions.append(execute) 562 else: 563 execute()
Indicate the title to assign the window in the operating system.
Indicate the human-readable string title to assign to the sketch window.
Arguments:
- title: The text of the title.
Show the sketch.
Show the sketch to the user and, if applicable, start the draw loop specified by set_fps. For Sketch2DApp, will execute any waiting drawing instructions provided to the sketch prior to showing. This is conceptually the same as "starting" the sketch.
Arguments:
- ax: The container into which the sketch should be shown. Currently only supported for Sketch2DStatic. Optional and ignored on most renderers.
Show the sketch and quit immediatley afterwards.
Show the sketch to the user and quit immediately afterwards, a routine potentially useful for testing.
Change the location of the origin.
Change the transform matrix such that any drawing afterwards is moved by a set amount.
Arguments:
- x: The number of pixels to offset horizontally.
- y: The number of pixels to offset vertically.
581 def rotate(self, angle_mirror: float): 582 angle = -1 * angle_mirror 583 angle_rad = self._convert_to_radians(angle) 584 self._transformer.rotate(angle_rad)
Rotate around the current origin.
Change the transform matrix such that any drawing afterwards is rotated around the current origin clock-wise.
Arguments:
- angle: The angle by which to rotate.
Scale outwards from the current origin.
Change the transform matrix such that any drawing afterwards is scaled from the current origin.
Arguments:
- scale: The factor by which to scale where values over 1 scale up and less than 1 scale down. A value of 1 will have no effect.
Inherited Members
- sketchingpy.abstracted.Sketch
- set_fill
- clear_fill
- set_stroke
- clear_stroke
- set_arc_mode
- set_ellipse_mode
- set_rect_mode
- draw_pixel
- start_shape
- set_stroke_weight
- set_text_font
- set_text_align
- set_map_pan
- set_map_zoom
- set_map_placement
- convert_geo_to_pixel
- start_geo_polygon
- push_map
- pop_map
- parse_geojson
- set_image_mode
- set_angle_mode
- push_style
- pop_style
- get_millis_shown
798class PygameSketchStateMachine(sketchingpy.state_struct.SketchStateMachine): 799 """Implementation of SketchStateMachine for Pygame types.""" 800 801 def __init__(self): 802 """Create a new state machine for Pygame-based sketches.""" 803 super().__init__() 804 self._fill_native = pygame.Color(super().get_fill()) 805 self._stroke_native = pygame.Color(super().get_stroke()) 806 self._font_cache = {} 807 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 808 809 def set_fill(self, fill: str): 810 super().set_fill(fill) 811 self._fill_native = pygame.Color(super().get_fill()) 812 813 def get_fill_native(self): 814 return self._fill_native 815 816 def set_stroke(self, stroke: str): 817 super().set_stroke(stroke) 818 self._stroke_native = pygame.Color(super().get_stroke()) 819 820 def get_stroke_native(self): 821 return self._stroke_native 822 823 def get_text_font_native(self): 824 font = self.get_text_font() 825 key = '%s.%d' % (font.get_identifier(), font.get_size()) 826 827 if key not in self._font_cache: 828 new_font = PIL.ImageFont.truetype(font.get_identifier(), font.get_size()) 829 self._font_cache[key] = new_font 830 831 return self._font_cache[key] 832 833 def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign): 834 super().set_text_align(text_align) 835 self._text_align_native = self._transform_text_align(super().get_text_align_native()) 836 837 def get_text_align_native(self): 838 return self._text_align_native 839 840 def _transform_text_align(self, 841 text_align: sketchingpy.state_struct.TextAlign) -> sketchingpy.state_struct.TextAlign: 842 843 HORIZONTAL_ALIGNS = { 844 sketchingpy.const.LEFT: 'l', 845 sketchingpy.const.CENTER: 'm', 846 sketchingpy.const.RIGHT: 'r' 847 } 848 849 VERTICAL_ALIGNS = { 850 sketchingpy.const.TOP: 't', 851 sketchingpy.const.CENTER: 'm', 852 sketchingpy.const.BASELINE: 's', 853 sketchingpy.const.BOTTOM: 'b' 854 } 855 856 return sketchingpy.state_struct.TextAlign( 857 HORIZONTAL_ALIGNS[text_align.get_horizontal_align()], 858 VERTICAL_ALIGNS[text_align.get_vertical_align()] 859 )
Implementation of SketchStateMachine for Pygame types.
801 def __init__(self): 802 """Create a new state machine for Pygame-based sketches.""" 803 super().__init__() 804 self._fill_native = pygame.Color(super().get_fill()) 805 self._stroke_native = pygame.Color(super().get_stroke()) 806 self._font_cache = {} 807 self._text_align_native = self._transform_text_align(super().get_text_align_native())
Create a new state machine for Pygame-based sketches.
809 def set_fill(self, fill: str): 810 super().set_fill(fill) 811 self._fill_native = pygame.Color(super().get_fill())
Set the fill color.
Set the color to use for filling shapes and figures.
Arguments:
- fill: Name of the color or a hex code.
Get the renderer-native version of the fill color.
Returns:
Renderer-specific value. Undefined if get_fill_enabled() is False.
816 def set_stroke(self, stroke: str): 817 super().set_stroke(stroke) 818 self._stroke_native = pygame.Color(super().get_stroke())
Set the stroke color.
Set the color to use for drawing outlines for shapes and figures as well as lines.
Arguments:
- stroke: Name of the color or a hex code.
Get the renderer-native version of the stroke color.
Returns:
Renderer-specific value. Undefined if get_stroke_enabled() is False.
823 def get_text_font_native(self): 824 font = self.get_text_font() 825 key = '%s.%d' % (font.get_identifier(), font.get_size()) 826 827 if key not in self._font_cache: 828 new_font = PIL.ImageFont.truetype(font.get_identifier(), font.get_size()) 829 self._font_cache[key] = new_font 830 831 return self._font_cache[key]
Get the type and size for text drawing.
Returns:
Renderer-specific value.
833 def set_text_align(self, text_align: sketchingpy.state_struct.TextAlign): 834 super().set_text_align(text_align) 835 self._text_align_native = self._transform_text_align(super().get_text_align_native())
Indicate the alignment to use when drawing text.
Arguments:
- text_align: Structure describing horizontal and vertical text alignment.
Get the alignment to use when drawing text.
Returns:
Renderer-specific value.
Inherited Members
- sketchingpy.state_struct.SketchStateMachine
- get_fill
- get_fill_enabled
- clear_fill
- get_stroke
- get_stroke_enabled
- clear_stroke
- set_arc_mode
- get_arc_mode
- get_arc_mode_native
- set_ellipse_mode
- get_ellipse_mode
- get_ellipse_mode_native
- set_rect_mode
- get_rect_mode
- get_rect_mode_native
- set_stroke_weight
- get_stroke_weight
- get_stroke_weight_native
- set_text_font
- get_text_font
- get_text_align
- set_image_mode
- get_image_mode
- get_image_mode_native
- set_angle_mode
- get_angle_mode
- get_angle_mode_native
862class PygameImage(sketchingpy.abstracted.Image): 863 """Strategy implementation for Pygame images.""" 864 865 def __init__(self, src: str): 866 """Create a new image. 867 868 Args: 869 src: Path to the image. 870 """ 871 super().__init__(src) 872 self._native = pygame.image.load(self.get_src()) 873 self._converted = False 874 875 def get_width(self) -> float: 876 return self._native.get_rect().width 877 878 def get_height(self) -> float: 879 return self._native.get_rect().height 880 881 def resize(self, width: float, height: float): 882 self._native = pygame.transform.scale(self._native, (width, height)) 883 884 def get_native(self): 885 if not self._converted: 886 self._native.convert_alpha() 887 888 return self._native 889 890 def get_is_loaded(self): 891 return True
Strategy implementation for Pygame images.
865 def __init__(self, src: str): 866 """Create a new image. 867 868 Args: 869 src: Path to the image. 870 """ 871 super().__init__(src) 872 self._native = pygame.image.load(self.get_src()) 873 self._converted = False
Create a new image.
Arguments:
- src: Path to the image.
Get the width of this image in pixels.
Returns:
Horizontal width of this image.
Get the height of this image in pixels.
Returns:
Vertical height of this image.
881 def resize(self, width: float, height: float): 882 self._native = pygame.transform.scale(self._native, (width, height))
Resize this image by scaling.
Arguments:
- width: The new desired width of this image in pixels.
- height: The new desired height of this image in pixels.
884 def get_native(self): 885 if not self._converted: 886 self._native.convert_alpha() 887 888 return self._native
Access the underlying native version of this image.
Returns:
Renderer specific native version.
Determine if this image has finished loading.
Returns:
True if loaded and ready to draw. False otherwise.
Inherited Members
894class PygameMouse(sketchingpy.control_struct.Mouse): 895 """Strategy implementation for Pygame-based mouse access.""" 896 897 def __init__(self): 898 """Create a new mouse strategy using Pygame.""" 899 super().__init__() 900 self._press_callback = None 901 self._release_callback = None 902 903 def get_pointer_x(self): 904 return pygame.mouse.get_pos()[0] 905 906 def get_pointer_y(self): 907 return pygame.mouse.get_pos()[1] 908 909 def get_buttons_pressed(self) -> sketchingpy.control_struct.Buttons: 910 is_left_pressed = pygame.mouse.get_pressed()[0] 911 is_right_pressed = pygame.mouse.get_pressed()[2] 912 buttons_clicked = [] 913 914 if is_left_pressed: 915 buttons_clicked.append(sketchingpy.const.MOUSE_LEFT_BUTTON) 916 917 if is_right_pressed: 918 buttons_clicked.append(sketchingpy.const.MOUSE_RIGHT_BUTTON) 919 920 return map(lambda x: sketchingpy.control_struct.Button(x), buttons_clicked) 921 922 def on_button_press(self, callback: sketchingpy.control_struct.MouseCallback): 923 self._press_callback = callback 924 925 def on_button_release(self, callback: sketchingpy.control_struct.MouseCallback): 926 self._release_callback = callback 927 928 def report_mouse_down(self, event): 929 if self._press_callback is None: 930 return 931 932 if event.button == 1: 933 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_LEFT_BUTTON) 934 self._press_callback(button) 935 elif event.button == 3: 936 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_RIGHT_BUTTON) 937 self._press_callback(button) 938 939 def report_mouse_up(self, event): 940 if self._release_callback is None: 941 return 942 943 if event.button == 1: 944 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_LEFT_BUTTON) 945 self._release_callback(button) 946 elif event.button == 3: 947 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_RIGHT_BUTTON) 948 self._release_callback(button)
Strategy implementation for Pygame-based mouse access.
897 def __init__(self): 898 """Create a new mouse strategy using Pygame.""" 899 super().__init__() 900 self._press_callback = None 901 self._release_callback = None
Create a new mouse strategy using Pygame.
Get the x coordinate of the mouse pointer.
Get the current horizontal coordinate of the mouse pointer or, in the case of a touchscreen, the point of last touch input if available. Defaults to 0 if no mouse events have been seen.
Returns:
The horizontal coordinate of the mouse pointer.
Get the y coordinate of the mouse pointer.
Get the current vertical coordinate of the mouse pointer or, in the case of a touchscreen, the point of last touch input if available. Defaults to 0 if no mouse events have been seen.
Returns:
The vertical coordinate of the mouse pointer.
928 def report_mouse_down(self, event): 929 if self._press_callback is None: 930 return 931 932 if event.button == 1: 933 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_LEFT_BUTTON) 934 self._press_callback(button) 935 elif event.button == 3: 936 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_RIGHT_BUTTON) 937 self._press_callback(button)
939 def report_mouse_up(self, event): 940 if self._release_callback is None: 941 return 942 943 if event.button == 1: 944 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_LEFT_BUTTON) 945 self._release_callback(button) 946 elif event.button == 3: 947 button = sketchingpy.control_struct.Button(sketchingpy.const.MOUSE_RIGHT_BUTTON) 948 self._release_callback(button)
951class PygameKeyboard(sketchingpy.control_struct.Keyboard): 952 """Strategy implementation for Pygame-based keyboard access.""" 953 954 def __init__(self): 955 """Create a new keyboard strategy using Pygame.""" 956 super().__init__() 957 self._pressed = set() 958 self._press_callback = None 959 self._release_callback = None 960 961 def get_keys_pressed(self) -> sketchingpy.control_struct.Buttons: 962 return map(lambda x: sketchingpy.control_struct.Button(x), self._pressed) 963 964 def on_key_press(self, callback: sketchingpy.control_struct.KeyboardCallback): 965 self._press_callback = callback 966 967 def on_key_release(self, callback: sketchingpy.control_struct.KeyboardCallback): 968 self._release_callback = callback 969 970 def report_key_down(self, event): 971 mapped = sketchingpy.sketch2d_keymap.KEY_MAP.get(event.key, None) 972 973 if mapped is None: 974 return 975 976 self._pressed.add(mapped) 977 978 if self._press_callback is not None: 979 button = sketchingpy.control_struct.Button(mapped) 980 self._press_callback(button) 981 982 def report_key_up(self, event): 983 mapped = sketchingpy.sketch2d_keymap.KEY_MAP.get(event.key, None) 984 985 if mapped is None: 986 return 987 988 self._pressed.remove(mapped) 989 990 if self._release_callback is not None: 991 button = sketchingpy.control_struct.Button(mapped) 992 self._release_callback(button)
Strategy implementation for Pygame-based keyboard access.
954 def __init__(self): 955 """Create a new keyboard strategy using Pygame.""" 956 super().__init__() 957 self._pressed = set() 958 self._press_callback = None 959 self._release_callback = None
Create a new keyboard strategy using Pygame.
961 def get_keys_pressed(self) -> sketchingpy.control_struct.Buttons: 962 return map(lambda x: sketchingpy.control_struct.Button(x), self._pressed)
Get a list of keys currently pressed.
Get a list of keys as Buttons.
Returns:
Get list of buttons pressed.
964 def on_key_press(self, callback: sketchingpy.control_struct.KeyboardCallback): 965 self._press_callback = callback
Callback for when a key is pressed.
Register a callback for when a key is pressed, calling a function with the key and keyboard. Will pass two arguments to that callback function: first a Button followed by a Keyboard. Will unregister prior callbacks for on_key_press.
Arguments:
- callback: The function to invoke when a key is pressed.
967 def on_key_release(self, callback: sketchingpy.control_struct.KeyboardCallback): 968 self._release_callback = callback
Callback for when a key is released.
Register a callback for when a key is unpressed, calling a function with the key and keyboard. Will pass two arguments to that callback function: first a Button followed by a Keyboard. Will unregister prior callbacks for on_key_release.
Arguments:
- callback: The function to invoke when a key is released.
970 def report_key_down(self, event): 971 mapped = sketchingpy.sketch2d_keymap.KEY_MAP.get(event.key, None) 972 973 if mapped is None: 974 return 975 976 self._pressed.add(mapped) 977 978 if self._press_callback is not None: 979 button = sketchingpy.control_struct.Button(mapped) 980 self._press_callback(button)
982 def report_key_up(self, event): 983 mapped = sketchingpy.sketch2d_keymap.KEY_MAP.get(event.key, None) 984 985 if mapped is None: 986 return 987 988 self._pressed.remove(mapped) 989 990 if self._release_callback is not None: 991 button = sketchingpy.control_struct.Button(mapped) 992 self._release_callback(button)
995class AppDialogLayer(sketchingpy.dialog_struct.DialogLayer): 996 """Dialog / simple UI layer for local apps.""" 997 998 def __init__(self, sketch: Sketch2DApp): 999 """"Initialize tkinter but hide the root window.""" 1000 self._sketch = sketch 1001 self._sketch_size = self._sketch._get_window_size() 1002 self._manager = pygame_gui.UIManager(self._sketch_size) 1003 self._callback = None # type: ignore 1004 self._dialog = None # type: ignore 1005 1006 def get_manager(self): 1007 return self._manager 1008 1009 def get_dialog(self): 1010 return self._dialog 1011 1012 def report_close(self, event): 1013 if self._callback: 1014 self._callback(event) 1015 1016 def show_alert(self, message: str, callback: typing.Optional[typing.Callable[[], None]] = None): 1017 self._dispose_dialog() 1018 self._set_dialog(pygame_gui.windows.UIMessageWindow( 1019 rect=pygame.Rect( 1020 self._sketch_size[0] / 2 - 150, 1021 self._sketch_size[1] / 2 - 150, 1022 300, 1023 300 1024 ), 1025 html_message=message, 1026 manager=self._manager 1027 )) 1028 1029 def outer_callback(event): 1030 if event.type == pygame_gui._constants.UI_BUTTON_PRESSED and callback: 1031 callback() 1032 1033 self._callback = outer_callback # type: ignore 1034 1035 def show_prompt(self, message: str, 1036 callback: typing.Optional[typing.Callable[[str], None]] = None): 1037 self._set_dialog(sketchingpy.pygame_prompt.PygameGuiPrompt( # type: ignore 1038 rect=pygame.Rect( 1039 self._sketch_size[0] / 2 - 150, 1040 self._sketch_size[1] / 2 - 150, 1041 300, 1042 300 1043 ), 1044 action_long_desc=message, 1045 manager=self._manager, 1046 window_title='Prompt' 1047 )) 1048 1049 def outer_callback(event): 1050 if event.type == pygame_gui._constants.UI_CONFIRMATION_DIALOG_CONFIRMED: 1051 callback(str(self._dialog.get_text())) 1052 1053 self._callback = outer_callback # type: ignore 1054 1055 def get_file_save_location(self, 1056 callback: typing.Optional[typing.Callable[[str], None]] = None): 1057 self._dispose_dialog() 1058 self._set_dialog(pygame_gui.windows.ui_file_dialog.UIFileDialog( 1059 rect=pygame.Rect( 1060 self._sketch_size[0] / 2 - 150, 1061 self._sketch_size[1] / 2 - 150, 1062 300, 1063 300 1064 ), 1065 manager=self._manager, 1066 allow_existing_files_only=False, 1067 window_title='Save' 1068 )) 1069 self._callback = self._make_file_dialog_callback(callback) # type: ignore 1070 1071 def get_file_load_location(self, 1072 callback: typing.Optional[typing.Callable[[str], None]] = None): 1073 self._dispose_dialog() 1074 self._set_dialog(pygame_gui.windows.ui_file_dialog.UIFileDialog( 1075 rect=pygame.Rect( 1076 self._sketch_size[0] / 2 - 150, 1077 self._sketch_size[1] / 2 - 150, 1078 300, 1079 300 1080 ), 1081 manager=self._manager, 1082 allow_existing_files_only=False, 1083 window_title='Load' 1084 )) 1085 self._callback = self._make_file_dialog_callback(callback) # type: ignore 1086 1087 def _make_file_dialog_callback(self, inner_callback): 1088 def callback(event): 1089 if event.type == pygame_gui.UI_FILE_DIALOG_PATH_PICKED: 1090 inner_callback(str(self._dialog.current_file_path)) 1091 1092 return callback 1093 1094 def _dispose_dialog(self): 1095 if self._dialog: 1096 self._dialog.kill() 1097 1098 def _set_dialog(self, new_dialog): 1099 self._dialog = new_dialog # type: ignore
Dialog / simple UI layer for local apps.
998 def __init__(self, sketch: Sketch2DApp): 999 """"Initialize tkinter but hide the root window.""" 1000 self._sketch = sketch 1001 self._sketch_size = self._sketch._get_window_size() 1002 self._manager = pygame_gui.UIManager(self._sketch_size) 1003 self._callback = None # type: ignore 1004 self._dialog = None # type: ignore
"Initialize tkinter but hide the root window.
1016 def show_alert(self, message: str, callback: typing.Optional[typing.Callable[[], None]] = None): 1017 self._dispose_dialog() 1018 self._set_dialog(pygame_gui.windows.UIMessageWindow( 1019 rect=pygame.Rect( 1020 self._sketch_size[0] / 2 - 150, 1021 self._sketch_size[1] / 2 - 150, 1022 300, 1023 300 1024 ), 1025 html_message=message, 1026 manager=self._manager 1027 )) 1028 1029 def outer_callback(event): 1030 if event.type == pygame_gui._constants.UI_BUTTON_PRESSED and callback: 1031 callback() 1032 1033 self._callback = outer_callback # type: ignore
Show an alert dialog box.
Arguments:
- callback: Method to invoke when the box closes.
- message: The string to show the user.
1035 def show_prompt(self, message: str, 1036 callback: typing.Optional[typing.Callable[[str], None]] = None): 1037 self._set_dialog(sketchingpy.pygame_prompt.PygameGuiPrompt( # type: ignore 1038 rect=pygame.Rect( 1039 self._sketch_size[0] / 2 - 150, 1040 self._sketch_size[1] / 2 - 150, 1041 300, 1042 300 1043 ), 1044 action_long_desc=message, 1045 manager=self._manager, 1046 window_title='Prompt' 1047 )) 1048 1049 def outer_callback(event): 1050 if event.type == pygame_gui._constants.UI_CONFIRMATION_DIALOG_CONFIRMED: 1051 callback(str(self._dialog.get_text())) 1052 1053 self._callback = outer_callback # type: ignore
Get a string input from the user.
Arguments:
- message: The message to display to the user within the dialog.
- callback: Method to invoke when the box closes with a single string parameter provided by the user. Not invoked if cancelled.
1055 def get_file_save_location(self, 1056 callback: typing.Optional[typing.Callable[[str], None]] = None): 1057 self._dispose_dialog() 1058 self._set_dialog(pygame_gui.windows.ui_file_dialog.UIFileDialog( 1059 rect=pygame.Rect( 1060 self._sketch_size[0] / 2 - 150, 1061 self._sketch_size[1] / 2 - 150, 1062 300, 1063 300 1064 ), 1065 manager=self._manager, 1066 allow_existing_files_only=False, 1067 window_title='Save' 1068 )) 1069 self._callback = self._make_file_dialog_callback(callback) # type: ignore
Get either the filename or full location for saving a file.
Arguments:
- callback: Method to invoke when the box closes with single string parameter which is the filename or the path selected by the user. Not invoked if cancelled.
1071 def get_file_load_location(self, 1072 callback: typing.Optional[typing.Callable[[str], None]] = None): 1073 self._dispose_dialog() 1074 self._set_dialog(pygame_gui.windows.ui_file_dialog.UIFileDialog( 1075 rect=pygame.Rect( 1076 self._sketch_size[0] / 2 - 150, 1077 self._sketch_size[1] / 2 - 150, 1078 300, 1079 300 1080 ), 1081 manager=self._manager, 1082 allow_existing_files_only=False, 1083 window_title='Load' 1084 )) 1085 self._callback = self._make_file_dialog_callback(callback) # type: ignore
Get either the filename or full location for opening a file.
Arguments:
- callback: Method to invoke when the box closes with single string parameter which is the filename or the path selected by the user. Not invoked if cancelled.