├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── _config.yml ├── albow ├── ItemRefInsertionException.py ├── References.py ├── __init__.py ├── choices │ ├── ImageMultiChoice.py │ ├── MultiChoice.py │ ├── TextMultiChoice.py │ └── __init__.py ├── containers │ ├── GridView.py │ ├── ImageArray.py │ ├── PaletteView.py │ ├── TabPanel.py │ └── __init__.py ├── core │ ├── CoreUtilities.py │ ├── DummySound.py │ ├── RectUtility.py │ ├── ResourceUtility.py │ ├── ScheduledCall.py │ ├── Scheduler.py │ ├── UserEventCall.py │ ├── __init__.py │ ├── exceptions │ │ ├── ApplicationException.py │ │ ├── CancelException.py │ │ └── __init__.py │ └── ui │ │ ├── AlbowEventLoop.py │ │ ├── AlbowRect.py │ │ ├── EventLoopParams.py │ │ ├── Predictor.py │ │ ├── RootWidget.py │ │ ├── Screen.py │ │ ├── Shell.py │ │ ├── Widget.py │ │ └── __init__.py ├── demo │ ├── AlbowDemo.py │ ├── AlbowDemoScreen.py │ ├── AlbowDemoShell.py │ ├── AlbowDemoSpecialTabs.py │ ├── DemoShell.py │ ├── GridDebugMain.py │ ├── GridDebugShell.py │ ├── Resources │ │ ├── fonts │ │ │ ├── COPYRIGHT.TXT │ │ │ ├── README.TXT │ │ │ ├── RELEASENOTES.TXT │ │ │ ├── Vera.ttf │ │ │ └── VeraBd.ttf │ │ ├── images │ │ │ ├── EnterpriseD.png │ │ │ ├── KlingonD7.png │ │ │ ├── ball.gif │ │ │ ├── ball_disabled.png │ │ │ ├── ball_enabled.png │ │ │ ├── ball_highlighted.png │ │ │ ├── biohazard.png │ │ │ ├── fruit.png │ │ │ └── medfighter.png │ │ ├── music │ │ │ ├── ElecPiK04 75E-01.mp3 │ │ │ ├── ElecPiK04 75E-02.mp3 │ │ │ ├── ElecPiK04 75E-03.mp3 │ │ │ ├── ElecPiK04 75E-04.mp3 │ │ │ ├── README.txt │ │ │ └── Zoe_Poledouris_-_I_Have_Not_Been_To_Paradise_David_Bowie_Cover.mp3 │ │ └── text │ │ │ └── demo_text.txt │ ├── ScheduledEventTabPage.py │ ├── __init__.py │ ├── demo.py │ ├── loggingConfiguration.json │ ├── openGL │ │ ├── DemoButton.py │ │ ├── OrthoDemo.py │ │ ├── PerspectiveDemo.py │ │ ├── __init__.py │ │ └── demo3d.py │ ├── screens │ │ ├── BaseDemoScreen.py │ │ ├── DemoAnimationScreen.py │ │ ├── DemoAnimationWidget.py │ │ ├── DemoControlsScreen.py │ │ ├── DemoDialogScreen.py │ │ ├── DemoGridViewScreen.py │ │ ├── DemoImageArrayScreen.py │ │ ├── DemoListBoxScreen.py │ │ ├── DemoMenuBarScreen.py │ │ ├── DemoMultiChoiceScreen.py │ │ ├── DemoMusicScreen.py │ │ ├── DemoPaletteViewScreen.py │ │ ├── DemoTabPanelScreen.py │ │ ├── DemoTableScreen.py │ │ ├── DemoTextFieldsScreen.py │ │ ├── DemoUserEventsScreen.py │ │ ├── GridDebugScreen.py │ │ ├── LaunchDemosScreen.py │ │ └── __init__.py │ └── views │ │ ├── DemoGridView.py │ │ ├── DemoPaletteView.py │ │ ├── DemoTableView.py │ │ └── __init__.py ├── dialog │ ├── Dialog.py │ ├── DialogTitleBar.py │ ├── DialogUtilities.py │ ├── DirectoryPathView.py │ ├── FileDialog.py │ ├── FileDialogUtilities.py │ ├── FileListView.py │ ├── FileOpenDialog.py │ ├── FileSaveDialog.py │ ├── LookForFileDialog.py │ ├── Modal.py │ ├── TitledDialog.py │ └── __init__.py ├── input │ ├── Field.py │ ├── FloatField.py │ ├── IntField.py │ ├── TextEditor.py │ ├── TextField.py │ └── __init__.py ├── layout │ ├── Column.py │ ├── Frame.py │ ├── Grid.py │ ├── Row.py │ ├── RowOrColumn.py │ └── __init__.py ├── media │ ├── EnableMusicControl.py │ ├── MusicOptionsDialog.py │ ├── MusicUtilities.py │ ├── MusicVolumeControl.py │ ├── PlayList.py │ ├── Sound.py │ └── __init__.py ├── menu │ ├── Menu.py │ ├── MenuBar.py │ ├── MenuItem.py │ └── __init__.py ├── openGL │ ├── GLOrtho.py │ ├── GLPerspective.py │ ├── GLSurface.py │ ├── GLViewport.py │ └── __init__.py ├── profiling.py ├── table │ ├── TableColumn.py │ ├── TableHeaderView.py │ ├── TableRowBase.py │ ├── TableRowView.py │ ├── TableView.py │ └── __init__.py ├── text │ ├── Page.py │ ├── TextScreen.py │ └── __init__.py ├── themes │ ├── FontProperty.py │ ├── Theme.py │ ├── ThemeError.py │ ├── ThemeLoader.py │ ├── ThemeProperty.py │ ├── __init__.py │ └── resources │ │ ├── Vera.ttf │ │ ├── VeraBd.ttf │ │ ├── __init__.py │ │ └── default-theme.ini ├── utils.py ├── vectors.py ├── version.py └── widgets │ ├── Button.py │ ├── ButtonBase.py │ ├── CheckBox.py │ ├── CheckControl.py │ ├── CheckWidget.py │ ├── Control.py │ ├── Image.py │ ├── ImageButton.py │ ├── Label.py │ ├── ListBox.py │ ├── RadioButton.py │ ├── RadioControl.py │ ├── TextBox.py │ ├── ValueDisplay.py │ ├── WidgetUtilities.py │ └── __init__.py ├── docs ├── Albow.put ├── ImportProject.put └── Tables.put ├── requirements.txt ├── scripts ├── cleanup.sh ├── packageme.sh ├── pushtoprod.sh ├── pushtotest.sh └── runtests.sh ├── setup.py └── test ├── .gitignore ├── DummyControl.py ├── DummyVehicle.py ├── DummyWidget.py ├── RunTests.py ├── TestBase.py ├── TestFindResources.py ├── TestPredictor.py ├── TestReferences.py ├── TestResourceUtility.py ├── TestScheduledCall.py ├── TestScheduler.py ├── TestTheme.py ├── TestThemeProperty.py ├── __init__.py ├── testLoggingConfig.json └── testresources ├── TestSound.mp3 └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | .DS_Store 4 | /html/ 5 | /build/ 6 | /dist/ 7 | /python3_albow.egg-info/ 8 | /site/ 9 | /venv-pyenv-3.9.1/ 10 | /venv-pyenv-3.9.0/ 11 | /venv-pyenv-3.8.5/ 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | # os: osx 3 | 4 | # osx_image: xcode11.2 5 | 6 | language: python 7 | 8 | cache: pip 9 | 10 | python: 11 | - "3.8.5" 12 | 13 | # command to install dependencies 14 | install: 15 | - pip install -r requirements.txt 16 | 17 | 18 | # command to run tests 19 | script: ${TRAVIS_BUILD_DIR}/scripts/runtests.sh 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Humberto A Sanchez II 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include albow/demo/*.json 2 | exclude albow/version.py 3 | graft albow/demo/Resources 4 | graft albow/themes/resources -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/hasii2011/OrthogonalDrawing.svg?branch=master)](https://travis-ci.com/hasii2011/OrthogonalDrawing) 2 | 3 | [![PyPI version](https://badge.fury.io/py/python3-albow.svg)](https://badge.fury.io/py/python3-albow) 4 | 5 | This project is a port of Gregory Ewing's Albow project (https://www.pygame.org/project/338/4687) that was only 6 | compatible with Python 2 and is unsupported since 2014. 7 | 8 | Ported to Python 3 and extended by [Humberto A. Sanchez II](https://www.linkedin.com/in/hasii/) 9 | Updated documentation at: https://hasii2011.github.io 10 | 11 | and at 12 | 13 | https://albow-30-documentation.deelo.cloud 14 | 15 | 16 | Install in your virtual environment with 17 | 18 | pip3 install python3-albow 19 | 20 | 21 | I saw recently where Gregory has albow working on Python 3. Maybe, I should abandon this project 22 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /albow/ItemRefInsertionException.py: -------------------------------------------------------------------------------- 1 | 2 | class ItemRefInsertionException(Exception): 3 | """ 4 | This exception is thrown if there is a problem when updating an ItemRef 5 | object 6 | """ 7 | def __init__(self, theIndex: int, theMessage: str = None): 8 | """ 9 | 10 | Args: 11 | theIndex: The index where the insertion error occurred 12 | 13 | theMessage: A custom message 14 | """ 15 | self.index = theIndex 16 | 17 | if theMessage is None: 18 | self.message = f"Can't insert item at {theIndex}" 19 | else: 20 | self.message = f"{theMessage}: at index: {theIndex}" 21 | -------------------------------------------------------------------------------- /albow/choices/ImageMultiChoice.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Surface 3 | from pygame import Rect 4 | 5 | from albow.utils import blit_in_rect 6 | 7 | from albow.choices.MultiChoice import MultiChoice 8 | 9 | 10 | class ImageMultiChoice(MultiChoice): 11 | """ 12 | ImageMultichoice is a Multichoice control that displays its values in the form of images. 13 | """ 14 | highlight_style = 'fill' 15 | sel_color = (255, 192, 19) 16 | margin = 5 17 | 18 | def __init__(self, images, values, **kwds): 19 | """ 20 | Initialises the control with the given images and corresponding values. 21 | 22 | Args: 23 | images: The images we want displayed 24 | 25 | values: The values to return when the image is selected 26 | 27 | **kwds: 28 | """ 29 | image0 = images[0] 30 | w, h = image0.get_size() 31 | d = 2 * self.predict(kwds, 'margin') 32 | cell_size = w + d, h + d 33 | # 34 | # Python 3 update 35 | # 36 | super().__init__(cell_size, values, **kwds) 37 | 38 | self.images = images 39 | 40 | def draw_item(self, surface: Surface, imageIndex: int, rect: Rect): 41 | 42 | image = self.images[imageIndex] 43 | blit_in_rect(surface, image, rect, self.align, self.margin) 44 | 45 | def draw_prehighlight(self, surf: Surface, theItemNumber: int, theRect: Rect): 46 | 47 | color = self.sel_color 48 | surf.fill(color, theRect) 49 | -------------------------------------------------------------------------------- /albow/choices/MultiChoice.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.locals import K_LEFT 3 | from pygame.locals import K_RIGHT 4 | 5 | from albow.widgets.Control import Control 6 | from albow.containers.PaletteView import PaletteView 7 | from albow.themes.ThemeProperty import ThemeProperty 8 | 9 | 10 | class MultiChoice(PaletteView, Control): 11 | 12 | highlight_color = ThemeProperty('highlight_color') 13 | cell_margin = ThemeProperty('cell_margin') 14 | 15 | align = 'c' 16 | tab_stop = True 17 | 18 | def __init__(self, cell_size, values, **kwds): 19 | PaletteView.__init__(self, cell_size, 1, len(values), **kwds) 20 | self.values = values 21 | 22 | def num_items(self): 23 | return len(self.values) 24 | 25 | def item_is_selected(self, n): 26 | return self.get_value() == self.values[n] 27 | 28 | def click_item(self, n, e): 29 | if self.tab_stop: 30 | self.focus() 31 | self.set_value(self.values[n]) 32 | 33 | def draw(self, surf): 34 | if self.has_focus(): 35 | surf.fill(self.highlight_color) 36 | PaletteView.draw(self, surf) 37 | 38 | def key_down(self, e): 39 | k = e.key 40 | if k == K_LEFT: 41 | self.change_value(-1) 42 | elif k == K_RIGHT: 43 | self.change_value(1) 44 | else: 45 | PaletteView.key_down(self, e) 46 | 47 | def change_value(self, d): 48 | values = self.values 49 | if values: 50 | n = len(values) 51 | value = self.get_value() 52 | try: 53 | i = values.index(value) 54 | except ValueError: 55 | if d < 0: 56 | i = 0 57 | else: 58 | i = n - 1 59 | else: 60 | i = max(0, min(n - 1, i + d)) 61 | self.set_value(values[i]) 62 | -------------------------------------------------------------------------------- /albow/choices/TextMultiChoice.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import draw 3 | from pygame import Surface 4 | from pygame import Rect 5 | 6 | from albow.utils import blit_in_rect 7 | 8 | 9 | from albow.choices.MultiChoice import MultiChoice 10 | 11 | 12 | class TextMultiChoice(MultiChoice): 13 | """ 14 | TextMultichoice is a Multichoice control that displays its values in the form of text. 15 | 16 | .. Note:: 17 | In addition to the highlight styles defined by PaletteView, 18 | TextMultichoice also provides 'arrows', which highlights the selected value with a pair of 19 | arrowheads above and below. 20 | 21 | """ 22 | def __init__(self, values, labels=None, **kwds): 23 | """ 24 | 25 | Initializes the control with the given values and corresponding labels. If no labels are specified, 26 | they are derived by applying str() to the values 27 | 28 | Args: 29 | values: The values 30 | 31 | labels: The displayed associated labels 32 | 33 | **kwds: 34 | """ 35 | 36 | if not labels: 37 | labels = map(str, values) 38 | font = self.predict_font(kwds) 39 | # d = 2 * self.predict(kwds, 'margin') 40 | cd = 2 * self.predict(kwds, 'cell_margin') 41 | wmax = 0 42 | hmax = 0 43 | 44 | for (w, h) in map(font.size, labels): 45 | wmax = max(wmax, w) 46 | hmax = max(hmax, h) 47 | cw = wmax + cd 48 | ch = hmax + cd 49 | 50 | super().__init__((cw, ch), values, **kwds) 51 | 52 | self.labels = labels 53 | 54 | def draw_item(self, theSurface: Surface, n: int, theRect: Rect): 55 | 56 | buf = self.font.render(self.labels[n], True, self.fg_color) 57 | blit_in_rect(theSurface, buf, theRect, self.align, self.margin) 58 | 59 | def draw_prehighlight(self, theSurface: Surface, theItemNumber: int, theRect: Rect): 60 | if self.highlight_style == 'arrows': 61 | self.draw_arrows(theSurface, theRect) 62 | else: 63 | MultiChoice.draw_prehighlight(self, theSurface, theItemNumber, theRect) 64 | 65 | def draw_arrows(self, theSurface: Surface, theRect: Rect): 66 | """ 67 | 68 | Args: 69 | theSurface: pygame surface to drawn 70 | 71 | theRect: The pygame rectangle to draw in 72 | 73 | """ 74 | m = self.margin 75 | color = self.sel_color or self.fg_color 76 | x, y = theRect.midtop 77 | pts = [(x - m, y - m), (x + m, y - m), (x, y)] 78 | draw.polygon(theSurface, color, pts) 79 | x, y = theRect.midbottom 80 | y -= 1 81 | pts = [(x - m, y + m), (x + m, y + m), (x, y)] 82 | draw.polygon(theSurface, color, pts) 83 | -------------------------------------------------------------------------------- /albow/choices/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | this package contains the 'choice" widgets 3 | """ -------------------------------------------------------------------------------- /albow/containers/GridView.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | from pygame import Surface 4 | 5 | from pygame.event import Event 6 | 7 | from albow.core.ui.Widget import Widget 8 | 9 | 10 | class GridView(Widget): 11 | """ 12 | The GridView class is an abstract base class for widgets that display information in a regular grid of 13 | rows and columns. Subclasses implement methods that define the size of the grid, for drawing a cell of 14 | the grid and for responding to mouse clicks in a cell. 15 | """ 16 | 17 | def __init__(self, cell_size, nrows, ncols, **kwds): 18 | """ 19 | Initializes the grid view with the specified cell_size, and a rect sized to show nrows rows and ncols 20 | columns of cells. 21 | 22 | Note that nrows and ncols are used only for calculating the initial size of the widget, and are not stored. 23 | 24 | Args: 25 | cell_size: The cell_size as a tuple (width, height) 26 | 27 | nrows: The # of rows 28 | 29 | ncols: The number of columns 30 | 31 | **kwds: Additional property values in keyword:value format 32 | 33 | """ 34 | # 35 | # Python 3 update 36 | # 37 | super().__init__(**kwds) 38 | self.cell_size = cell_size 39 | w, h = cell_size 40 | d = 2 * self.margin 41 | self.size = (w * ncols + d, h * nrows + d) 42 | self.cell_size = cell_size 43 | 44 | def draw(self, surface): 45 | # for row in xrange(self.num_rows()): 46 | # for col in xrange(self.num_cols()): 47 | # Python 3 update 48 | for row in range(self.num_rows()): 49 | for col in range(self.num_cols()): 50 | r = self.cell_rect(row, col) 51 | self.draw_cell(surface, row, col, r) 52 | 53 | def cell_rect(self, theRow: int, theColumn: int) -> Rect: 54 | """ 55 | Returns a rectangle covering the cell for row 'theRow' and column 'theColumn'. 56 | 57 | Args: 58 | theRow: The Row 59 | 60 | theColumn: The column 61 | 62 | Returns: A pygame rectangle 63 | 64 | """ 65 | w, h = self.cell_size 66 | d = self.margin 67 | x = theColumn * w + d 68 | y = theRow * h + d 69 | 70 | return Rect(x, y, w, h) 71 | 72 | def mouse_down(self, event): 73 | 74 | x, y = event.local 75 | w, h = self.cell_size 76 | W, H = self.size 77 | d = self.margin 78 | if d <= x < W - d and d <= y < H - d: 79 | row = (y - d) // h 80 | col = (x - d) // w 81 | self.click_cell(row, col, event) 82 | 83 | # 84 | # Abstract methods follow 85 | # 86 | 87 | def click_cell(self, theRow: int, theColumn: int, theEvent: Event): 88 | pass 89 | 90 | def draw_cell(self, theSurface: Surface, theRow: int, theColumn: int, rect: Rect): 91 | pass 92 | 93 | def num_rows(self) -> int: 94 | """ 95 | 96 | Returns: The # of rows 97 | """ 98 | pass 99 | 100 | def num_cols(self) -> int: 101 | """ 102 | 103 | Returns: The # of columns 104 | """ 105 | pass 106 | -------------------------------------------------------------------------------- /albow/containers/ImageArray.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | from pygame import Surface 4 | 5 | from albow.core.ResourceUtility import ResourceUtility 6 | 7 | 8 | class ImageArray: 9 | """ 10 | An ImageArray is an indexed collection of images created by dividing up a master image into equal-sized subimages. 11 | 12 | Image arrays can be one-dimensional or two-dimensional. A one-dimensional image array has its 13 | subimages arranged horizontally in the master image and is indexed by an integer. A two-dimensional image array is 14 | indexed by a ``tuple (row, col)``. 15 | 16 | """ 17 | ncols = 0 18 | nrows = 0 19 | size = (0,0) 20 | image_array_cache = {} 21 | 22 | def __init__(self, image, shape): 23 | """ 24 | Constructs an image array from the given image, which should be a Surface. 25 | 26 | Args: 27 | image: The surface that is the image 28 | 29 | shape: The shape is either an integer for a one-dimensional image array, 30 | or a tuple (num_rows, num_cols) for a two-dimensional image array. 31 | """ 32 | self.image = image 33 | self.shape = shape 34 | if isinstance(shape, tuple): 35 | self.nrows, self.ncols = shape 36 | else: 37 | #self.nrows = 1 38 | self.nrows = None 39 | self.ncols = shape 40 | iwidth, iheight = image.get_size() 41 | self.size = iwidth // self.ncols, iheight // (self.nrows or 1) 42 | 43 | def __len__(self): 44 | """ 45 | Returns the shape of the image array (an integer if one-dimensional, or a 2-tuple if two-dimensional). 46 | 47 | Returns: The shape 48 | 49 | """ 50 | result = self.shape 51 | if isinstance(result, tuple): 52 | raise TypeError("Can only use len() on 1-dimensional image array") 53 | return result 54 | 55 | def __nonzero__(self): 56 | return True 57 | 58 | def __getitem__(self, theIndex) -> Surface: 59 | """ 60 | Returns a subsurface for the image at index theIndex of a one-dimensional image array. 61 | 62 | Args: 63 | theIndex: The index of the image to return 64 | 65 | Returns: 66 | 67 | """ 68 | image = self.image 69 | nrows = self.nrows 70 | ncols = self.ncols 71 | if nrows is None: 72 | row = 0 73 | col = theIndex 74 | else: 75 | row, col = theIndex 76 | width, height = self.size 77 | left = width * col 78 | top = height * row 79 | return image.subsurface(left, top, width, height) 80 | 81 | def get_rect(self) -> Rect: 82 | """ 83 | Creates and returns a bounding rectangle for one of the subimages, with top left corner (0, 0). 84 | 85 | Returns: The bounding rectangle 86 | """ 87 | return Rect((0, 0), self.size) 88 | 89 | @classmethod 90 | def get_image_array(cls, name, shape, **kwds) -> 'ImageArray': 91 | """ 92 | Creates and returns an ImageArray from an image resource with the given name. The ImageArray is cached, and 93 | subsequent calls with the same name will return the cached object. Additional keyword arguments are 94 | passed on to ``albow.core.ResourceUtility``.get_image(). 95 | 96 | Args: 97 | name: The image name 98 | 99 | shape: The shape 100 | 101 | **kwds: 102 | 103 | Returns: 104 | 105 | """ 106 | result = cls.image_array_cache.get(name) 107 | if result is None: 108 | result = ImageArray(ResourceUtility.get_image(name, **kwds), shape) 109 | cls.image_array_cache[name] = result 110 | return result 111 | -------------------------------------------------------------------------------- /albow/containers/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | This package contains the Albow containers 3 | """ -------------------------------------------------------------------------------- /albow/core/CoreUtilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. WARNING:: 3 | This is warning text 4 | 5 | .. ATTENTION:: 6 | This is attention text 7 | 8 | .. CAUTION:: 9 | This is caution text 10 | 11 | .. DANGER:: 12 | This is danger text 13 | 14 | .. ERROR:: 15 | This is error text 16 | 17 | .. HINT:: 18 | This is hint text 19 | 20 | .. IMPORTANT:: 21 | This is important text 22 | 23 | .. NOTE:: 24 | This is note text 25 | 26 | .. TIP:: 27 | This is tip text 28 | 29 | """ 30 | from time import time 31 | 32 | from pygame.locals import * 33 | 34 | from pygame.time import get_ticks 35 | 36 | MOD_CMD = KMOD_LCTRL | KMOD_RCTRL | KMOD_LMETA | KMOD_RMETA 37 | 38 | MODIFIERS = dict( 39 | shift=False, 40 | ctrl=False, 41 | alt=False, 42 | meta=False, 43 | ) 44 | 45 | MOD_KEYS = { 46 | K_LSHIFT: 'shift', K_RSHIFT: 'shift', 47 | K_LCTRL: 'ctrl', K_RCTRL: 'ctrl', 48 | K_LALT: 'alt', K_RALT: 'alt', 49 | K_LMETA: 'meta', K_RMETA: 'meta', 50 | } 51 | 52 | 53 | class CoreUtilities: 54 | 55 | timeBase = 0 56 | 57 | """ 58 | A static class for some leftover module functions 59 | """ 60 | @staticmethod 61 | def set_modifier(key, value): 62 | attr = MOD_KEYS.get(key) 63 | if attr: 64 | MODIFIERS[attr] = value 65 | 66 | @staticmethod 67 | def add_modifiers(event): 68 | d = event.dict 69 | d.update(MODIFIERS) 70 | d['cmd'] = event.ctrl or event.meta 71 | 72 | @staticmethod 73 | def init_timebase(): 74 | 75 | CoreUtilities.timeBase = time() * 1000.0 - get_ticks() 76 | print(f"timeBase: {str(CoreUtilities.timeBase)}") 77 | -------------------------------------------------------------------------------- /albow/core/DummySound.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class DummySound: 4 | """ 5 | A dummy sound class for what? 6 | """ 7 | def fadeout(self, x): pass 8 | 9 | def get_length(self): return 0.0 10 | 11 | def get_num_channels(self): return 0 12 | 13 | def get_volume(self): return 0.0 14 | 15 | def play(self, *args): pass 16 | 17 | def set_volume(self, x): pass 18 | 19 | def stop(self): pass 20 | -------------------------------------------------------------------------------- /albow/core/RectUtility.py: -------------------------------------------------------------------------------- 1 | 2 | class RectUtility: 3 | 4 | @staticmethod 5 | def rect_property(name): 6 | def get(self): 7 | return getattr(self._rect, name) 8 | 9 | def set(self, value): 10 | r = self._rect 11 | old_size = r.size 12 | setattr(r, name, value) 13 | new_size = r.size 14 | # 15 | # Python 3 update 16 | # i f old_size <> new_size: 17 | if old_size != new_size: 18 | # 19 | # Method signature changed since tuples not allowed to be passed 20 | # 21 | # self._resized(old_size) 22 | self._resized(old_size[0], old_size[1]) 23 | 24 | return property(get, set) 25 | -------------------------------------------------------------------------------- /albow/core/ScheduledCall.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Callable 3 | 4 | 5 | class ScheduledCall: 6 | """ 7 | Object to get tasks periodically scheduled 8 | """ 9 | def __init__(self, timeToExecute: float, func: Callable, interval: float): 10 | """ 11 | Use `albow.core.Scheduler.Scheduler.timestamp` as the `timeToExecute` value 12 | 13 | Args: 14 | timeToExecute: When we want this function executed 15 | 16 | func: The function to execute 17 | 18 | interval: How long to delay after -timeToExecute_ has elapsed 19 | """ 20 | self.time = timeToExecute 21 | self.func = func 22 | self.interval = interval 23 | 24 | def __ne__(self, other): 25 | return not (self == other) 26 | 27 | def __lt__(self, other): 28 | return self.time < other.time 29 | 30 | def __eq__(self, theOtherOne): 31 | """ 32 | Overrides the default implementation 33 | """ 34 | if isinstance(theOtherOne, ScheduledCall): 35 | if self.time == theOtherOne.time and \ 36 | self.func == theOtherOne.func and \ 37 | self.interval == theOtherOne.interval: 38 | return True 39 | 40 | return False 41 | 42 | def __le__(self, other): 43 | return self.time <= other.time 44 | 45 | def __gt__(self, other): 46 | return self.time > other.time, other 47 | 48 | def __ge__(self, other): 49 | return self.time >= other.time, other 50 | 51 | def __str__(self): 52 | 53 | formattedMe: str = f"time: {self.time}, func: {self.func}, interval: {self.interval}" 54 | return formattedMe 55 | -------------------------------------------------------------------------------- /albow/core/UserEventCall.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Callable 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class UserEventCall: 9 | 10 | func: Callable 11 | userEvent: int 12 | -------------------------------------------------------------------------------- /albow/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the core Albow classes 3 | """ -------------------------------------------------------------------------------- /albow/core/exceptions/ApplicationException.py: -------------------------------------------------------------------------------- 1 | 2 | class ApplicationException(Exception): 3 | pass 4 | -------------------------------------------------------------------------------- /albow/core/exceptions/CancelException.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.exceptions.ApplicationException import ApplicationException 3 | 4 | 5 | class CancelException(ApplicationException): 6 | """ 7 | Raising CancelException causes control to be returned silently to the event loop. It can be used to cancel 8 | an operation, for example in response to cancelling a modal dialog. 9 | """ 10 | pass 11 | -------------------------------------------------------------------------------- /albow/core/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ -------------------------------------------------------------------------------- /albow/core/ui/EventLoopParams.py: -------------------------------------------------------------------------------- 1 | 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class EventLoopParams: 7 | 8 | use_sleep: bool 9 | relative_pause: bool 10 | do_draw: bool 11 | relative_warmup: int 12 | last_click_time: int 13 | num_clicks: int 14 | -------------------------------------------------------------------------------- /albow/core/ui/Predictor.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from albow.themes.Theme import Theme 5 | from albow.core.ui.Widget import Widget 6 | 7 | 8 | class Predictor: 9 | 10 | def __init__(self, theWidget: Widget): 11 | 12 | self.logger = logging.getLogger(__name__) 13 | self.widget = theWidget 14 | 15 | def predict(self, kwds, name): 16 | try: 17 | return kwds[name] 18 | except KeyError: 19 | return Theme.getThemeRoot().get(self.widget.__class__, name) 20 | 21 | def predict_attr(self, kwds, name): 22 | try: 23 | return kwds[name] 24 | except KeyError: 25 | return getattr(self.widget, name) 26 | 27 | def init_attr(self, kwds, name): 28 | try: 29 | return kwds.pop(name) 30 | except KeyError: 31 | return getattr(self.widget, name) 32 | 33 | def predict_font(self, kwds, name='font'): 34 | return kwds.get(name) or Theme.getThemeRoot().get_font(self.widget.__class__, name) 35 | -------------------------------------------------------------------------------- /albow/core/ui/Screen.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.event import Event 3 | 4 | from albow.core.ui.Shell import Shell 5 | from albow.core.ui.Widget import Widget 6 | 7 | 8 | class Screen(Widget): 9 | """ 10 | Screen is an abstract base class for widgets to be uses as screens by a Shell. 11 | """ 12 | def __init__(self, shell: Shell, **kwds): 13 | """ 14 | Constructs a Screen associated with the given shell. 15 | Args: 16 | shell: The shell to associate with 17 | 18 | **kwds: 19 | """ 20 | # 21 | # Python 3 update 22 | super().__init__(shell.rect, **kwds) 23 | self.shell = shell 24 | self.center = shell.center 25 | 26 | def begin_frame(self): 27 | """Deprecated, use timer_event() instead.""" 28 | pass 29 | # 30 | # Abstract methods follow 31 | # 32 | 33 | def timer_event(self, event: Event): 34 | """ 35 | Called from the timer_event() method of the Shell when this screen is the current screen. The default 36 | implementation returns true so that a display update is performed. 37 | 38 | Args: 39 | event: 40 | 41 | """ 42 | self.begin_frame() 43 | return True 44 | 45 | def enter_screen(self): 46 | """ 47 | Called from the Shell after switching to this screen from another screen. 48 | """ 49 | pass 50 | 51 | def leave_screen(self): 52 | """ 53 | Called from the Shell before switching away from this screen to another screen. 54 | """ 55 | pass 56 | -------------------------------------------------------------------------------- /albow/core/ui/Shell.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.event import Event 3 | 4 | from pygame import Surface 5 | 6 | from albow.core.ui.RootWidget import RootWidget 7 | 8 | 9 | class Shell(RootWidget): 10 | """ 11 | The Shell class is an abstract class to use as a base for the outer shell of a game. It provides facilities 12 | for switching between a number of screens, such as menus, pages of instructions or the game itself. Screens 13 | are normally subclasses of the Screen widget. 14 | """ 15 | 16 | def __init__(self, surface: Surface, **kwds): 17 | """ 18 | Initializes the Shell with the given surface as its root surface (normally this will be the PyGame 19 | screen surface). 20 | 21 | Args: 22 | surface: A pygame surface 23 | **kwds: 24 | """ 25 | # 26 | # Python 3 update 27 | # 28 | super().__init__(surface, **kwds) 29 | self.current_screen = None 30 | 31 | def show_screen(self, new_screen): 32 | """ 33 | Hides the previous screen, if any, and shows the given widget as the new screen. The widget is displayed 34 | centered within the shell. 35 | 36 | The leave_screen() method of the previous screen is called before hiding it, and the enter_screen() method 37 | of the new screen is called after showing it. 38 | 39 | TODO fix screen imports shell & shell imports screen 40 | 41 | Args: 42 | new_screen: The new screen to display 43 | 44 | """ 45 | old_screen = self.current_screen 46 | if old_screen is not new_screen: 47 | if old_screen: 48 | old_screen.leave_screen() 49 | self.remove(old_screen) 50 | self.add(new_screen) 51 | self.current_screen = new_screen 52 | if new_screen: 53 | new_screen.focus() 54 | new_screen.enter_screen() 55 | self.invalidate() 56 | 57 | def timer_event(self, event: Event): 58 | """ 59 | Calls the timer_event() method of the current screen, if any, and returns its result. 60 | Args: 61 | event: An event 62 | 63 | Returns: The result 64 | 65 | """ 66 | screen = self.current_screen 67 | if screen: 68 | return screen.timer_event(event) 69 | 70 | def begin_frame(self): 71 | """ 72 | Deprecated, use timer_event() instead. 73 | """ 74 | screen = self.current_screen 75 | if screen: 76 | screen.begin_frame() 77 | 78 | def relative_mode(self): 79 | """ 80 | A Shell runs in relative input mode if the current screen does. 81 | """ 82 | screen = self.current_screen 83 | return screen and screen.relative_mode() 84 | -------------------------------------------------------------------------------- /albow/core/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ -------------------------------------------------------------------------------- /albow/demo/AlbowDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Albow Demonstration Program 3 | """ 4 | import json 5 | 6 | import pygame 7 | import logging.config 8 | 9 | from albow.themes.Theme import Theme 10 | from albow.themes.ThemeLoader import ThemeLoader 11 | 12 | JSON_LOGGING_CONFIG_FILENAME = "loggingConfiguration.json" 13 | 14 | # SCREEN_SIZE = (640, 480) 15 | # DISPLAY_FLAGS = pygame.RESIZABLE 16 | DEMO_WINDOW_TITLE = "Albow Demonstration 2.0" 17 | SCREEN_SIZE = (960, 480) 18 | DISPLAY_FLAGS = 0 19 | 20 | 21 | def main(): 22 | # 23 | # This has to be done as early as possible to affect the logging 24 | # statements in the class files 25 | # Pycharm gives a warning on the order of imports, Oh well 26 | # 27 | 28 | with open(JSON_LOGGING_CONFIG_FILENAME, 'r') as loggingConfigurationFile: 29 | configurationDictionary = json.load(loggingConfigurationFile) 30 | 31 | logging.config.dictConfig(configurationDictionary) 32 | logging.logProcesses = False 33 | logging.logThreads = False 34 | 35 | # 36 | # Have to get all the theme attributes defined first before 37 | # anything is imported with ThemeProperty attributes 38 | # 39 | themeLoader: ThemeLoader = ThemeLoader() 40 | themeLoader.load() 41 | themeRoot: Theme = themeLoader.themeRoot 42 | Theme.setThemeRoot(themeRoot) 43 | 44 | from albow.demo.AlbowDemoShell import AlbowDemoShell 45 | 46 | pygame.init() 47 | pygame.display.set_caption(f'{DEMO_WINDOW_TITLE}') 48 | 49 | # "file_handler": { 50 | # "class": "logging.FileHandler", 51 | # "level": "DEBUG", 52 | # "formatter": "simple", 53 | # "filename": "demo_logging.log", 54 | # "encoding": "utf8" 55 | # }, 56 | logger = logging.getLogger(__name__) 57 | display = pygame.display.set_mode(SCREEN_SIZE, DISPLAY_FLAGS) 58 | shellArgs = { 59 | 'margin': 5 60 | } 61 | shell = AlbowDemoShell(display, **shellArgs) 62 | 63 | logger.info("Starting %s", __name__) 64 | 65 | shell.run() 66 | 67 | 68 | if __name__ == '__main__': 69 | main() 70 | -------------------------------------------------------------------------------- /albow/demo/AlbowDemoShell.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Surface 3 | 4 | from albow.core.ui.Shell import Shell 5 | 6 | from albow.demo.AlbowDemoScreen import AlbowDemoScreen 7 | 8 | 9 | class AlbowDemoShell(Shell): 10 | 11 | FRAME_TIME = 30 12 | """ 13 | In milliseconds 14 | """ 15 | 16 | def __init__(self, theSurface: Surface, **kwds): 17 | 18 | super().__init__(theSurface, **kwds) 19 | 20 | self.set_timer(AlbowDemoShell.FRAME_TIME) 21 | 22 | self.screen: AlbowDemoScreen = AlbowDemoScreen(shell=self, theSurface=theSurface) 23 | self.show_screen(self.screen) 24 | -------------------------------------------------------------------------------- /albow/demo/AlbowDemoSpecialTabs.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.widgets.Label import Label 3 | 4 | from albow.layout.Column import Column 5 | from albow.layout.Row import Row 6 | 7 | from albow.demo.screens.DemoUserEventsScreen import DemoUserEventsScreen 8 | 9 | from albow.demo.ScheduledEventTabPage import ScheduledEventTabPage 10 | 11 | from albow.demo.views.DemoTableView import DemoTableView 12 | from albow.demo.views.DemoGridView import DemoGridView 13 | from albow.demo.views.DemoPaletteView import DemoPaletteView 14 | 15 | 16 | class AlbowDemoSpecialTabs: 17 | 18 | @classmethod 19 | def makeEventsTab(cls) -> Column: 20 | 21 | userEvents: Column = DemoUserEventsScreen.makeContents() 22 | scheduledEventsTabPage: ScheduledEventTabPage = ScheduledEventTabPage(height=userEvents.height, width=userEvents.width) 23 | contentAttrs = { 24 | 'align': "c", 25 | 'margin': 10 26 | } 27 | eventTab: Column = Column([userEvents, scheduledEventsTabPage], **contentAttrs) 28 | 29 | from albow.demo.AlbowDemoScreen import AlbowDemoScreen 30 | 31 | AlbowDemoScreen.classScheduledEventsTabPage = scheduledEventsTabPage 32 | return eventTab 33 | 34 | @classmethod 35 | def makeGridLikeTab(cls): 36 | 37 | gridTabAttrs = { 38 | 'align': "c", 39 | 'margin': 20 40 | } 41 | 42 | grid: DemoGridView = DemoGridView() 43 | lbl = Label("Cl1ck a Squ4r3") 44 | grid.output = lbl 45 | 46 | gridColumn: Column = Column([grid, lbl]) 47 | 48 | table: DemoTableView = DemoTableView() 49 | palette: DemoPaletteView = DemoPaletteView() 50 | 51 | gridTab: Row = Row([table, palette, gridColumn], **gridTabAttrs) 52 | 53 | return gridTab 54 | -------------------------------------------------------------------------------- /albow/demo/DemoShell.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | from albow.text.TextScreen import TextScreen 4 | 5 | from albow.demo.screens.DemoMultiChoiceScreen import DemoMultiChoiceScreen 6 | from albow.demo.screens.DemoTableScreen import DemoTableScreen 7 | from albow.demo.screens.DemoTabPanelScreen import DemoTabPanelScreen 8 | from albow.demo.screens.DemoGridViewScreen import DemoGridViewScreen 9 | from albow.demo.screens.DemoPaletteViewScreen import DemoPaletteViewScreen 10 | from albow.demo.screens.DemoImageArrayScreen import DemoImageArrayScreen 11 | from albow.demo.screens.DemoAnimationScreen import DemoAnimationScreen 12 | from albow.demo.screens.DemoControlsScreen import DemoControlsScreen 13 | from albow.demo.screens.DemoTextFieldsScreen import DemoTextFieldsScreen 14 | from albow.demo.screens.DemoDialogScreen import DemoDialogScreen 15 | from albow.demo.screens.DemoMenuBarScreen import DemoMenuBarScreen 16 | from albow.demo.screens.DemoMusicScreen import DemoMusicScreen 17 | from albow.demo.screens.DemoListBoxScreen import DemoListBoxScreen 18 | from albow.demo.screens.DemoUserEventsScreen import DemoUserEventsScreen 19 | 20 | from albow.demo.screens.LaunchDemosScreen import LaunchDemosScreen 21 | 22 | DEMO_FRAME_TIME = 50 # ms 23 | 24 | 25 | class DemoShell(Shell): 26 | 27 | """ 28 | Shell 29 | """ 30 | def __init__(self, display): 31 | """ 32 | 33 | Args: 34 | display: 35 | """ 36 | # 37 | # Python 3 update 38 | # 39 | super().__init__(display) 40 | 41 | self.text_screen = TextScreen(self, "demo_text.txt") 42 | self.fields_screen = DemoTextFieldsScreen(self) 43 | self.controls_screen = DemoControlsScreen(self) 44 | self.anim_screen = DemoAnimationScreen(self) 45 | self.grid_screen = DemoGridViewScreen(self) 46 | self.palette_screen = DemoPaletteViewScreen(self) 47 | self.image_array_screen = DemoImageArrayScreen(self) 48 | self.dialog_screen = DemoDialogScreen(self) 49 | self.tab_panel_screen = DemoTabPanelScreen(self) 50 | self.table_screen = DemoTableScreen(self) 51 | self.multiChoiceScreen = DemoMultiChoiceScreen(self) 52 | self.menuBarScreen = DemoMenuBarScreen(self) 53 | self.musicScreen = DemoMusicScreen(self) 54 | self.listBoxScreen = DemoListBoxScreen(self) 55 | self.userEventsScreen = DemoUserEventsScreen(self) 56 | 57 | self.menu_screen = LaunchDemosScreen(self) # Do this last 58 | self.set_timer(DEMO_FRAME_TIME) 59 | self.show_menu() 60 | 61 | def show_menu(self): 62 | self.show_screen(self.menu_screen) 63 | 64 | def __repr__(self): 65 | return self.__class__.__name__ 66 | -------------------------------------------------------------------------------- /albow/demo/GridDebugMain.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Albow Demonstration Program 3 | """ 4 | import json 5 | 6 | import pygame 7 | import logging.config 8 | 9 | from albow.themes.Theme import Theme 10 | from albow.themes.ThemeLoader import ThemeLoader 11 | 12 | JSON_LOGGING_CONFIG_FILENAME = "loggingConfiguration.json" 13 | 14 | # SCREEN_SIZE = (640, 480) 15 | # DISPLAY_FLAGS = pygame.RESIZABLE 16 | DEMO_WINDOW_TITLE = "Grid Debug Main" 17 | SCREEN_SIZE = (480, 640) 18 | DISPLAY_FLAGS = 0 19 | 20 | 21 | def main(): 22 | # 23 | # This has to be done as early as possible to affect the logging 24 | # statements in the class files 25 | # Pycharm gives a warning on the order of imports, Oh well 26 | # 27 | 28 | with open(JSON_LOGGING_CONFIG_FILENAME, 'r') as loggingConfigurationFile: 29 | configurationDictionary = json.load(loggingConfigurationFile) 30 | 31 | logging.config.dictConfig(configurationDictionary) 32 | logging.logProcesses = False 33 | logging.logThreads = False 34 | 35 | # 36 | # Have to get all the theme attributes defined first before 37 | # anything is imported with ThemeProperty attributes 38 | # 39 | themeLoader: ThemeLoader = ThemeLoader() 40 | themeLoader.load() 41 | themeRoot: Theme = themeLoader.themeRoot 42 | Theme.setThemeRoot(themeRoot) 43 | 44 | from albow.demo.GridDebugShell import GridDebugShell 45 | 46 | pygame.init() 47 | pygame.display.set_caption("%s" % DEMO_WINDOW_TITLE) 48 | 49 | # "file_handler": { 50 | # "class": "logging.FileHandler", 51 | # "level": "DEBUG", 52 | # "formatter": "simple", 53 | # "filename": "demo_logging.log", 54 | # "encoding": "utf8" 55 | # }, 56 | logger = logging.getLogger(__name__) 57 | display = pygame.display.set_mode(SCREEN_SIZE, DISPLAY_FLAGS) 58 | shell = GridDebugShell(display) 59 | 60 | logger.info("Starting %s", __name__) 61 | 62 | shell.run() 63 | 64 | 65 | if __name__ == '__main__': 66 | main() 67 | -------------------------------------------------------------------------------- /albow/demo/GridDebugShell.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.demo.screens.GridDebugScreen import GridDebugScreen 5 | 6 | DEMO_FRAME_TIME = 50 # ms 7 | 8 | 9 | class GridDebugShell(Shell): 10 | 11 | """ 12 | Shell 13 | """ 14 | def __init__(self, display): 15 | """ 16 | 17 | Args: 18 | display: 19 | """ 20 | # 21 | # Python 3 update 22 | # 23 | super().__init__(display) 24 | 25 | self.menu_screen = GridDebugScreen(self) # Do this last 26 | self.set_timer(DEMO_FRAME_TIME) 27 | self.show_menu() 28 | 29 | def show_menu(self): 30 | self.show_screen(self.menu_screen) 31 | 32 | def __repr__(self): 33 | return self.__class__.__name__ 34 | -------------------------------------------------------------------------------- /albow/demo/Resources/fonts/README.TXT: -------------------------------------------------------------------------------- 1 | Contained herin is the Bitstream Vera font family. 2 | 3 | The Copyright information is found in the COPYRIGHT.TXT file (along 4 | with being incoporated into the fonts themselves). 5 | 6 | The releases notes are found in the file "RELEASENOTES.TXT". 7 | 8 | We hope you enjoy Vera! 9 | 10 | Bitstream, Inc. 11 | The Gnome Project 12 | -------------------------------------------------------------------------------- /albow/demo/Resources/fonts/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/fonts/Vera.ttf -------------------------------------------------------------------------------- /albow/demo/Resources/fonts/VeraBd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/fonts/VeraBd.ttf -------------------------------------------------------------------------------- /albow/demo/Resources/images/EnterpriseD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/EnterpriseD.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/KlingonD7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/KlingonD7.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/ball.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/ball.gif -------------------------------------------------------------------------------- /albow/demo/Resources/images/ball_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/ball_disabled.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/ball_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/ball_enabled.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/ball_highlighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/ball_highlighted.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/biohazard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/biohazard.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/fruit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/fruit.png -------------------------------------------------------------------------------- /albow/demo/Resources/images/medfighter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/images/medfighter.png -------------------------------------------------------------------------------- /albow/demo/Resources/music/ElecPiK04 75E-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/music/ElecPiK04 75E-01.mp3 -------------------------------------------------------------------------------- /albow/demo/Resources/music/ElecPiK04 75E-02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/music/ElecPiK04 75E-02.mp3 -------------------------------------------------------------------------------- /albow/demo/Resources/music/ElecPiK04 75E-03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/music/ElecPiK04 75E-03.mp3 -------------------------------------------------------------------------------- /albow/demo/Resources/music/ElecPiK04 75E-04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/music/ElecPiK04 75E-04.mp3 -------------------------------------------------------------------------------- /albow/demo/Resources/music/README.txt: -------------------------------------------------------------------------------- 1 | Free music samples from: 2 | 3 | https://www.musicradar.com/news/tech/sampleradar-166-free-lounge-samples-488005 4 | 5 | These were originally in some advanced .wav format that pygame could not play. 6 | I converted them to .mp3 format using ffmpeg 7 | 8 | ffmpeg -i ElecPiK04\ 75E-04.wav new04.mp3 9 | ffmpeg -i ElecPiK04\ 75E-03.wav new03.mp3 10 | ffmpeg -i ElecPiK04\ 75E-02.wav new02.mp3 11 | ffmpeg -i ElecPiK04\ 75E-01.wav new01.mp3 12 | 13 | See https://ffmpeg.org 14 | 15 | The Zoe Poledouris soundtrack is on several web sites on the 'Net; Google it, Bing it, Yahoo it. :-) 16 | -------------------------------------------------------------------------------- /albow/demo/Resources/music/Zoe_Poledouris_-_I_Have_Not_Been_To_Paradise_David_Bowie_Cover.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/demo/Resources/music/Zoe_Poledouris_-_I_Have_Not_Been_To_Paradise_David_Bowie_Cover.mp3 -------------------------------------------------------------------------------- /albow/demo/Resources/text/demo_text.txt: -------------------------------------------------------------------------------- 1 | Text Screen 2 | 3 | This is a demonstration text screen. You can imagine 4 | some kind of exciting introductory paragraph to your 5 | game here. 6 | 7 | If you want, you can have more than one paragraph of 8 | text on a page. 9 | 10 | PAGE 11 | 12 | Second Page 13 | 14 | This is the second page of the demo text screen. It's 15 | here so you can see how the menu at the bottom looks 16 | with all three buttons enabled. 17 | 18 | PAGE 19 | 20 | Last Page 21 | 22 | Three shall be the number of demo pages, and the 23 | number of demo pages shall be three. There shall 24 | not be four, nor shall there be two, except that 25 | they be followed by a third. Five is right out. 26 | -------------------------------------------------------------------------------- /albow/demo/ScheduledEventTabPage.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from pygame.font import Font 5 | 6 | from albow.core.Scheduler import Scheduler 7 | from albow.core.ScheduledCall import ScheduledCall 8 | 9 | from albow.core.ui.Widget import Widget 10 | 11 | from albow.core.ResourceUtility import ResourceUtility 12 | 13 | from albow.themes.Theme import Theme 14 | 15 | from albow.layout.Column import Column 16 | 17 | from albow.widgets.TextBox import TextBox 18 | from albow.widgets.Label import Label 19 | 20 | 21 | class ScheduledEventTabPage(Widget): 22 | 23 | DELAY_3_SECS = 3 * 1000 24 | DELAY_6_SECS = 6 * 1000 25 | REPEAT = True 26 | 27 | def __init__(self, **kwds): 28 | 29 | super().__init__(**kwds) 30 | 31 | self.logger = logging.getLogger(__name__) 32 | self.textBox = TextBox() 33 | 34 | f1: Font = ResourceUtility.get_font(16, Theme.BUILT_IN_BOLD_FONT) 35 | textBoxTitle: Label = Label(text="Scheduled Events", font=f1) 36 | 37 | contentAttrs = { 38 | 'align': 'c' 39 | } 40 | 41 | contents: Column = Column([textBoxTitle, self.textBox], **contentAttrs) 42 | self.token3 = None 43 | self.token6 = None 44 | self.add_centered(contents) 45 | 46 | def createScheduledEvents(self): 47 | 48 | self.token3: ScheduledCall = Scheduler.schedule_call(delay=ScheduledEventTabPage.DELAY_3_SECS, 49 | func=self.scheduledMethod, 50 | repeat=ScheduledEventTabPage.REPEAT) 51 | 52 | self.token6: ScheduledCall = Scheduler.schedule_call(delay=ScheduledEventTabPage.DELAY_3_SECS, 53 | func=self.scheduledMethod, 54 | repeat=ScheduledEventTabPage.REPEAT) 55 | 56 | def cancelScheduledEvents(self): 57 | 58 | Scheduler.cancel_call(token=self.token3) 59 | Scheduler.cancel_call(token=self.token6) 60 | 61 | def scheduledMethod(self): 62 | 63 | ts = Scheduler.timestamp() 64 | cbText = f"I have been called at: {ts}" 65 | self.logger.info(cbText) 66 | 67 | oldText = self.textBox.getText() 68 | 69 | cbText = f"{oldText}\n{cbText}" 70 | 71 | self.textBox.setText(cbText) 72 | -------------------------------------------------------------------------------- /albow/demo/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | This package contains the demonstration programs and the supporting classes 3 | """ -------------------------------------------------------------------------------- /albow/demo/demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Albow Demonstration Program 3 | """ 4 | import json 5 | 6 | import pygame 7 | import logging.config 8 | 9 | from albow.themes.Theme import Theme 10 | from albow.themes.ThemeLoader import ThemeLoader 11 | 12 | JSON_LOGGING_CONFIG_FILENAME = "loggingConfiguration.json" 13 | 14 | # SCREEN_SIZE = (640, 480) 15 | # DISPLAY_FLAGS = pygame.RESIZABLE 16 | DEMO_WINDOW_TITLE = "Albow Demonstration" 17 | SCREEN_SIZE = (480, 640) 18 | DISPLAY_FLAGS = 0 19 | 20 | 21 | def main(): 22 | # 23 | # This has to be done as early as possible to affect the logging 24 | # statements in the class files 25 | # Pycharm gives a warning on the order of imports, Oh well 26 | # 27 | 28 | with open(JSON_LOGGING_CONFIG_FILENAME, 'r') as loggingConfigurationFile: 29 | configurationDictionary = json.load(loggingConfigurationFile) 30 | 31 | logging.config.dictConfig(configurationDictionary) 32 | logging.logProcesses = False 33 | logging.logThreads = False 34 | 35 | # 36 | # Have to get all the theme attributes defined first before 37 | # anything is imported with ThemeProperty attributes 38 | # 39 | themeLoader: ThemeLoader = ThemeLoader() 40 | themeLoader.load() 41 | themeRoot: Theme = themeLoader.themeRoot 42 | Theme.setThemeRoot(themeRoot) 43 | 44 | from albow.demo.DemoShell import DemoShell 45 | 46 | pygame.init() 47 | pygame.display.set_caption("%s" % DEMO_WINDOW_TITLE) 48 | 49 | # "file_handler": { 50 | # "class": "logging.FileHandler", 51 | # "level": "DEBUG", 52 | # "formatter": "simple", 53 | # "filename": "demo_logging.log", 54 | # "encoding": "utf8" 55 | # }, 56 | logger = logging.getLogger(__name__) 57 | display = pygame.display.set_mode(SCREEN_SIZE, DISPLAY_FLAGS) 58 | shell = DemoShell(display) 59 | 60 | logger.info("Starting %s", __name__) 61 | 62 | shell.run() 63 | 64 | 65 | if __name__ == '__main__': 66 | main() 67 | -------------------------------------------------------------------------------- /albow/demo/loggingConfiguration.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "disable_existing_loggers": "False", 4 | "formatters": { 5 | "simple": { 6 | "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 7 | }, 8 | "demoSimple": { 9 | "format": "%(asctime)s.%(msecs)06d - %(levelname)s: %(module)s: %(message)s", 10 | "datefmt" : "%H:%M:%S" 11 | } 12 | }, 13 | "handlers": { 14 | "consoleHandler": { 15 | "class": "logging.StreamHandler", 16 | "formatter": "demoSimple", 17 | "stream": "ext://sys.stdout" 18 | } 19 | }, 20 | "loggers": { 21 | "root": { 22 | "level": "INFO", 23 | "handlers": ["consoleHandler"], 24 | "propagate": "False" 25 | }, 26 | "albow": { 27 | "level": "WARNING", 28 | "handlers": ["consoleHandler"], 29 | "propagate": "False" 30 | }, 31 | "albow.themes": { 32 | "level": "WARNING", 33 | "propagate": "False" 34 | }, 35 | "__main__": { 36 | "level": "INFO", 37 | "propagate": "False" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /albow/demo/openGL/DemoButton.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.color import Color 3 | 4 | from albow.widgets.Button import Button 5 | 6 | 7 | class DemoButton(Button): 8 | bg_color = Color("brown") 9 | border_width = 2 10 | margin = 4 11 | -------------------------------------------------------------------------------- /albow/demo/openGL/OrthoDemo.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | 4 | from albow.openGL.GLOrtho import GLOrtho 5 | 6 | from OpenGL.GL import glBegin 7 | from OpenGL.GL import glColor3f 8 | from OpenGL.GL import glVertex2f 9 | from OpenGL.GL import glEnd 10 | from OpenGL.GL import GL_TRIANGLES 11 | 12 | 13 | class OrthoDemo(GLOrtho): 14 | 15 | sat = 1.0 16 | 17 | def __init__(self): 18 | GLOrtho.__init__(self, Rect(0, 0, 200, 200), 0, 10, 0, 10) 19 | 20 | def gl_draw(self): 21 | s = 1.0 - self.sat 22 | glBegin(GL_TRIANGLES) 23 | glColor3f(1, s, s) 24 | glVertex2f(5, 10) 25 | glColor3f(s, 1, s) 26 | glVertex2f(0, 0) 27 | glColor3f(s, s, 1) 28 | glVertex2f(10, 0) 29 | glEnd() 30 | 31 | def lighter(self): 32 | self.sat = max(0.0, self.sat - 0.1) 33 | 34 | def darker(self): 35 | self.sat = min(1.0, self.sat + 0.1) 36 | -------------------------------------------------------------------------------- /albow/demo/openGL/PerspectiveDemo.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | 4 | from OpenGL.GL import glBegin 5 | from OpenGL.GL import glEnable 6 | from OpenGL.GL import glTranslatef 7 | from OpenGL.GL import glRotatef 8 | from OpenGL.GL import glColor3fv 9 | from OpenGL.GL import glVertex3fv 10 | 11 | from OpenGL.GL import glEnd 12 | from OpenGL.GL import GL_DEPTH_TEST 13 | from OpenGL.GL import GL_QUADS 14 | 15 | from albow.openGL.GLPerspective import GLPerspective 16 | 17 | cube_pts = [ 18 | (-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1), 19 | (-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1), 20 | ] 21 | 22 | cube_faces = [ 23 | (0, 3, 2, 1), (4, 5, 6, 7), 24 | (0, 1, 5, 4), (2, 3, 7, 6), 25 | (1, 2, 6, 5), (0, 4, 7, 3), 26 | ] 27 | 28 | cube_colors = [ 29 | (1, 0, 0), (0, 1, 0), (0.5, 0.75, 1), 30 | (1, 1, 0), (1, 0, 1), (0, 1, 1), 31 | ] 32 | 33 | 34 | class PerspectiveDemo(GLPerspective): 35 | 36 | arot = [30, 30, 0] 37 | 38 | def __init__(self): 39 | # 40 | # Python 3 update 41 | # 42 | super().__init__(Rect(0, 0, 200, 200)) 43 | 44 | def gl_draw(self): 45 | glEnable(GL_DEPTH_TEST) 46 | glTranslatef(0, 0, -10) 47 | glRotatef(self.arot[0], 1, 0, 0) 48 | glRotatef(self.arot[1], 0, 1, 0) 49 | glRotatef(self.arot[2], 0, 0, 1) 50 | glBegin(GL_QUADS) 51 | 52 | for aColor, face in zip(cube_colors, cube_faces): 53 | glColor3fv(aColor) 54 | for i in face: 55 | glVertex3fv(cube_pts[i]) 56 | glEnd() 57 | 58 | @staticmethod 59 | def mouse_down(e): 60 | print("Perspective: mouse_down: ray =", e.ray) 61 | 62 | def rotate(self, i): 63 | self.arot[i] = (self.arot[i] + 10) % 360 64 | -------------------------------------------------------------------------------- /albow/demo/openGL/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | This package contains the OpenGL demonstration classes 3 | """ -------------------------------------------------------------------------------- /albow/demo/openGL/demo3d.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Albow OpenGL Demonstration program 3 | """ 4 | import sys 5 | 6 | import json 7 | 8 | import logging.config 9 | 10 | import pygame 11 | 12 | from pygame import Surface 13 | 14 | from pygame.color import Color 15 | from pygame.locals import OPENGL 16 | from pygame.locals import DOUBLEBUF 17 | 18 | from albow.themes.Theme import Theme 19 | from albow.themes.ThemeLoader import ThemeLoader 20 | 21 | from albow.core.ui.RootWidget import RootWidget 22 | 23 | from albow.layout.Row import Row 24 | 25 | from albow.demo.openGL.OrthoDemo import OrthoDemo 26 | from albow.demo.openGL.PerspectiveDemo import PerspectiveDemo 27 | from albow.demo.openGL.DemoButton import DemoButton 28 | 29 | JSON_LOGGING_CONFIG_FILENAME = "../loggingConfiguration.json" 30 | 31 | def ortho_controls(ortho: OrthoDemo) -> Row: 32 | 33 | bl = DemoButton("Lighter", ortho.lighter) 34 | bd = DemoButton("Darker", ortho.darker) 35 | row = Row([bl, bd]) 36 | 37 | return row 38 | 39 | 40 | def perspective_controls(perspectiveDemo: PerspectiveDemo) -> Row: 41 | 42 | bx = DemoButton("RotX", lambda: perspectiveDemo.rotate(0)) 43 | by = DemoButton("RotY", lambda: perspectiveDemo.rotate(1)) 44 | bz = DemoButton("RotZ", lambda: perspectiveDemo.rotate(2)) 45 | row = Row([bx, by, bz]) 46 | 47 | return row 48 | 49 | 50 | def add_demo_widgets(root: RootWidget): 51 | 52 | orthoDemo: OrthoDemo = OrthoDemo() 53 | orthoDemo.topleft = (20, 20) 54 | root.add(orthoDemo) 55 | 56 | orthoControls = ortho_controls(orthoDemo) 57 | orthoControls.midtop = (orthoDemo.centerx, orthoDemo.bottom + 20) 58 | root.add(orthoControls) 59 | 60 | perspectiveDemo = PerspectiveDemo() 61 | perspectiveDemo.topleft = (orthoDemo.right + 20, orthoDemo.top) 62 | root.add(perspectiveDemo) 63 | 64 | perspectiveControls = perspective_controls(perspectiveDemo) 65 | perspectiveControls.midtop = (perspectiveDemo.centerx, perspectiveDemo.bottom + 20) 66 | root.add(perspectiveControls) 67 | 68 | 69 | screen_size = (640, 480) 70 | flags = 0 71 | 72 | 73 | def main(): 74 | 75 | with open(JSON_LOGGING_CONFIG_FILENAME, 'r') as loggingConfigurationFile: 76 | configurationDictionary = json.load(loggingConfigurationFile) 77 | 78 | logging.config.dictConfig(configurationDictionary) 79 | logging.logProcesses = False 80 | logging.logThreads = False 81 | 82 | # 83 | # Have to get all the theme attributes defined first before 84 | # anything is imported with ThemeProperty attributes 85 | # 86 | themeLoader: ThemeLoader = ThemeLoader() 87 | themeLoader.load() 88 | themeRoot: Theme = themeLoader.themeRoot 89 | Theme.setThemeRoot(themeRoot) 90 | 91 | pygame.init() 92 | 93 | gl_flags = flags | OPENGL 94 | 95 | if "-s" in sys.argv: 96 | print("Using single buffering") 97 | else: 98 | print("Using double buffering") 99 | gl_flags |= DOUBLEBUF 100 | 101 | display: Surface = pygame.display.set_mode(screen_size, gl_flags) 102 | root: RootWidget = RootWidget(display) 103 | root.bg_color: Color = Color("blue") 104 | 105 | add_demo_widgets(root) 106 | 107 | root.run() 108 | 109 | 110 | if __name__ == '__main__': 111 | main() 112 | -------------------------------------------------------------------------------- /albow/demo/screens/BaseDemoScreen.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from albow.core.ResourceUtility import ResourceUtility 5 | 6 | from albow.themes.Theme import Theme 7 | 8 | from albow.core.ui.Screen import Screen 9 | from albow.core.ui.Shell import Shell 10 | 11 | from albow.widgets.Button import Button 12 | 13 | 14 | class BaseDemoScreen(Screen): 15 | 16 | SMALL_BUTTON_TEXT_SIZE: int = 14 17 | SMALL_LABEL_TEXT_SIZE: int = 14 18 | DEFAULT_CONTENT_SPACING: int = 30 19 | 20 | def __init__(self, shell: Shell): 21 | 22 | self.logger = logging.getLogger(__name__) 23 | 24 | super().__init__(shell=shell) 25 | 26 | self.smallButtonFont = ResourceUtility.get_font(BaseDemoScreen.SMALL_BUTTON_TEXT_SIZE, Theme.BUILT_IN_FONT) 27 | self.labelFont = ResourceUtility.get_font(BaseDemoScreen.SMALL_LABEL_TEXT_SIZE, Theme.BUILT_IN_FONT) 28 | 29 | self.backButton = Button("Back", action=shell.show_menu, font=self.smallButtonFont) 30 | self.logger.debug(f"{self.backButton}") 31 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoAnimationScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.layout.Column import Column 5 | 6 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 7 | from albow.demo.screens.DemoAnimationWidget import DemoAnimationWidget 8 | 9 | 10 | class DemoAnimationScreen(BaseDemoScreen): 11 | """ 12 | Animation 13 | """ 14 | 15 | def __init__(self, shell: Shell): 16 | 17 | super().__init__(shell) 18 | 19 | animationWidget: DemoAnimationWidget = DemoAnimationWidget(shell) 20 | 21 | content: Column = Column([animationWidget, self.backButton]) 22 | self.add(content) 23 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoAnimationWidget.py: -------------------------------------------------------------------------------- 1 | 2 | import random 3 | 4 | from albow.core.ui.Widget import Widget 5 | 6 | POLYGON_BORDER_WIDTH: int = 2 7 | 8 | 9 | class DemoAnimationWidget(Widget): 10 | def __init__(self, parent, **attrs): 11 | 12 | super().__init__(**attrs) 13 | 14 | self.rect = parent.rect.inflate(-100, -100) 15 | w, h = self.size 16 | self.points = [[100, 50], [w - 50, 100], [50, h - 50]] 17 | 18 | def randomValue(): 19 | return random.randint(-5, 5) 20 | 21 | self.velocities = [ 22 | [randomValue(), randomValue()] for i in range(len(self.points)) 23 | ] 24 | 25 | def draw(self, surface): 26 | from pygame.draw import polygon 27 | polygon(surface, (128, 200, 255), self.points) 28 | polygon(surface, (255, 128, 0), self.points, POLYGON_BORDER_WIDTH) 29 | self.animate() 30 | 31 | def animate(self): 32 | r = self.rect 33 | w, h = r.size 34 | for p, v in zip(self.points, self.velocities): 35 | p[0] += v[0] 36 | p[1] += v[1] 37 | if not 0 <= p[0] <= w: 38 | v[0] = -v[0] 39 | if not 0 <= p[1] <= h: 40 | v[1] = -v[1] 41 | self.invalidate() 42 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoControlsScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from math import pi 3 | 4 | from albow.References import AttrRef 5 | 6 | from albow.input.FloatField import FloatField 7 | 8 | from albow.widgets.ValueDisplay import ValueDisplay 9 | 10 | from albow.widgets.Label import Label 11 | from albow.widgets.Button import Button 12 | from albow.widgets.RadioButton import RadioButton 13 | from albow.widgets.ImageButton import ImageButton 14 | 15 | from albow.layout.Column import Column 16 | from albow.layout.Grid import Grid 17 | from albow.layout.Row import Row 18 | 19 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 20 | 21 | 22 | class DemoControlsModel: 23 | 24 | width = 0.0 25 | height = 0.0 26 | shape = 'rectangle' 27 | 28 | def get_area(self): 29 | a = self.width * self.height 30 | shape = self.shape 31 | if shape == 'rectangle': 32 | return a 33 | elif shape == 'triangle': 34 | return 0.5 * a 35 | elif shape == 'ellipse': 36 | return 0.25 * pi * a 37 | 38 | area = property(get_area) 39 | 40 | 41 | class DemoControlsScreen(BaseDemoScreen): 42 | """ 43 | Controls 44 | """ 45 | 46 | def __init__(self, shell): 47 | 48 | """ 49 | 50 | :param shell: 51 | """ 52 | super().__init__(shell) 53 | 54 | contents = DemoControlsScreen.makeContents(self.backButton) 55 | 56 | self.add_centered(contents) 57 | 58 | @classmethod 59 | def makeContents(cls, backButton: Button = None) -> Column: 60 | 61 | model = DemoControlsModel() 62 | 63 | width_field = FloatField (ref=AttrRef(base=model, name='width')) 64 | height_field = FloatField (ref=AttrRef(base=model, name='height')) 65 | area_display = ValueDisplay(ref=AttrRef(base=model, name='area'), format="%.2f") 66 | shape = AttrRef(model, 'shape') 67 | shape_choices = Row([ 68 | RadioButton(setting='rectangle', ref=shape), Label("Rectangle"), 69 | RadioButton(setting='triangle', ref=shape), Label("Triangle"), 70 | RadioButton(setting='ellipse', ref=shape), Label("Ellipse"), 71 | ]) 72 | grid = Grid([ 73 | [Label("Width"), width_field], 74 | [Label("Height"), height_field], 75 | [Label("Shape"), shape_choices], 76 | [Label("Value Area"), area_display], 77 | ]) 78 | 79 | imgBtnBall: ImageButton = ImageButton(theImage="ball.gif") 80 | imgBtnHighlightedBall: ImageButton = ImageButton(theImage="ball.gif", highlightedBgImage="ball_highlighted.png") 81 | imgBtnDisabledBall: ImageButton = ImageButton(theImage="ball.gif", disabledBgImage="ball_disabled.png", enabled=False) 82 | imgBtnEnabledBall: ImageButton = ImageButton(theImage="ball.gif", enabledBgImage="ball_enabled.png", enabled=True) 83 | 84 | imgBtnTitle: Label = Label("Image Buttons") 85 | imgBtnGrid: Grid = Grid([ 86 | [Label("Regular"), imgBtnBall], 87 | [Label("Highlighted"), imgBtnHighlightedBall], 88 | [Label("Disabled"), imgBtnDisabledBall], 89 | [Label("Enabled"), imgBtnEnabledBall] 90 | ]) 91 | 92 | width_field.focus() 93 | 94 | if backButton is None: 95 | contents = Column([grid, imgBtnTitle, imgBtnGrid]) 96 | else: 97 | contents = Column([grid, imgBtnTitle, imgBtnGrid, backButton]) 98 | return contents 99 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoDialogScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.core.ResourceUtility import ResourceUtility 5 | 6 | from albow.layout.Column import Column 7 | 8 | from albow.widgets.Label import Label 9 | from albow.widgets.Button import Button 10 | 11 | from albow.dialog.TitledDialog import TitledDialog 12 | 13 | from albow.dialog.DialogUtilities import alert 14 | from albow.dialog.DialogUtilities import ask 15 | 16 | from albow.dialog.FileDialogUtilities import request_old_filename 17 | from albow.dialog.FileDialogUtilities import request_new_filename 18 | from albow.dialog.FileDialogUtilities import look_for_file_or_directory 19 | 20 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 21 | 22 | 23 | class DemoDialogScreen(BaseDemoScreen): 24 | """ 25 | Dialogs 26 | """ 27 | 28 | def __init__(self, shell: Shell): 29 | 30 | super().__init__(shell) 31 | 32 | contents = DemoDialogScreen.makeContents(self.backButton) 33 | self.add_centered(contents) 34 | 35 | @classmethod 36 | def makeContents(cls, backButton: Button = None) -> Column: 37 | 38 | menu = Column([ 39 | Button(text="Ask a Question", action=cls.test_ask), 40 | Button(text="Ask Old Filename", action=cls.test_old), 41 | Button(text="Ask New Filename", action=cls.test_new), 42 | Button(text="Look File/Directory", action=cls.test_lookfor), 43 | Button(text="Titled Dialog", action=cls.testTitledDialog), 44 | ], align='l', expand=3, equalize='w') 45 | 46 | if backButton is None: 47 | contents = Column([ 48 | menu, 49 | ], align='c', spacing=30) 50 | else: 51 | contents = Column([ 52 | Label("File Dialogs", font=ResourceUtility.get_font(18, "VeraBd.ttf")), 53 | menu, 54 | backButton, 55 | ], align='c', spacing=30) 56 | 57 | return contents 58 | 59 | @classmethod 60 | def test_ask(cls): 61 | response = ask("Do you like mustard and avocado ice cream?", ["Yes", "No", "Undecided"]) 62 | alert(f"You chose {response}.") 63 | 64 | @classmethod 65 | def test_old(cls): 66 | path = request_old_filename() 67 | if path: 68 | alert(f"You chose {path}.") 69 | else: 70 | alert("Cancelled.") 71 | 72 | @classmethod 73 | def test_new(cls): 74 | path = request_new_filename(prompt="Save booty as:", filename="treasure", suffix=".dat") 75 | if path: 76 | alert(f"You chose {path}.") 77 | else: 78 | alert("Cancelled.") 79 | 80 | @classmethod 81 | def test_lookfor(cls): 82 | path = look_for_file_or_directory(prompt="Please find 'Vera.ttf'", target="Vera.ttf") 83 | if path: 84 | alert(f"You chose {path}.") 85 | else: 86 | alert("Cancelled.") 87 | 88 | @classmethod 89 | def testTitledDialog(cls): 90 | 91 | longMsg: str = ( 92 | f'[.. to disarm the people; that it was the best and most effectual way to enslave them.' 93 | f' but that they should not do it openly, but weaken them, and let them sink gradually, ' 94 | f' by totally disusing and neglecting the militia. -- George Mason]' 95 | ) 96 | 97 | ttlDlg = TitledDialog(title='Three Button', thirdButtTxt='Just Quit', message='Three buttons with custom text') 98 | response = ttlDlg.present() 99 | alert(f'You responded: {response}') 100 | 101 | ttlDlg: TitledDialog = TitledDialog(title='Long wrapped message', message=longMsg) 102 | response = ttlDlg.present() 103 | alert(f'You responded: {response}') 104 | 105 | ttlDlg = TitledDialog(title='Chip8 Python', message='Version 0.5, by Humberto A. Sanchez II') 106 | response = ttlDlg.present() 107 | alert(f'You responded: {response}') 108 | 109 | 110 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoGridViewScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.widgets.Label import Label 5 | 6 | from albow.layout.Column import Column 7 | 8 | from albow.demo.views.DemoGridView import DemoGridView 9 | 10 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 11 | 12 | 13 | class DemoGridViewScreen(BaseDemoScreen): 14 | 15 | def __init__(self, shell: Shell): 16 | 17 | super().__init__(shell) 18 | 19 | grid = DemoGridView() 20 | lbl = Label("Cl1ck a Squ4r3") 21 | grid.output = lbl 22 | contents = Column([grid, lbl, self.backButton], align='c', spacing=30) 23 | self.add_centered(contents) 24 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoImageArrayScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.containers.ImageArray import ImageArray 5 | 6 | from albow.widgets.Label import Label 7 | from albow.widgets.Button import Button 8 | from albow.widgets.Image import Image 9 | 10 | from albow.layout.Column import Column 11 | 12 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 13 | 14 | 15 | class DemoImageArrayScreen(BaseDemoScreen): 16 | """ 17 | Image Array 18 | """ 19 | 20 | images: ImageArray = None 21 | image = None 22 | index: int = 0 23 | 24 | def __init__(self, shell: Shell): 25 | 26 | super().__init__(shell) 27 | 28 | contents: Column = DemoImageArrayScreen.makeContents(self.backButton) 29 | self.add_centered(contents) 30 | 31 | @classmethod 32 | def makeContents(cls, backButton: Button = None) -> Column: 33 | 34 | cls.images = ImageArray.get_image_array("fruit.png", shape=3, border=2) 35 | cls.image = Image(cls.images[0]) 36 | cls.index = 0 37 | 38 | if backButton is None: 39 | contents: Column = Column([cls.image, Button("Next Fruit", action=cls.next_image)], spacing=10) 40 | else: 41 | contentAttrs = { 42 | "align": "c", 43 | "margin": 10, 44 | 'border_width': 1 45 | } 46 | 47 | contents: Column = Column([ 48 | Label("Image Array"), 49 | cls.image, 50 | Button("Next Fruit", action=cls.next_image), 51 | backButton, 52 | ], spacing=10, **contentAttrs) 53 | 54 | return contents 55 | 56 | @classmethod 57 | def next_image(cls): 58 | 59 | cls.index = (cls.index + 1) % 3 60 | cls.image.image = cls.images[cls.index] 61 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoListBoxScreen.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from logging import Logger 4 | 5 | from albow.core.ResourceUtility import ResourceUtility 6 | 7 | from albow.core.ui.Shell import Shell 8 | from albow.core.ui.Widget import Widget 9 | 10 | from albow.layout.Column import Column 11 | 12 | from albow.themes.Theme import Theme 13 | 14 | from albow.widgets.Label import Label 15 | from albow.widgets.Button import Button 16 | from albow.widgets.ListBox import ListBox 17 | 18 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 19 | 20 | 21 | class DemoListBoxScreen(BaseDemoScreen): 22 | 23 | DEMO_LIST_DATA = [ 24 | "Humberto", "Fran", "Opie", "Gabriel (Gabby10Meows)" 25 | ] 26 | 27 | DEMO_LABEL_TEXT_SIZE = 18 28 | 29 | selectedLabel: Label = None 30 | logger: Logger = logging.getLogger(__name__) 31 | 32 | def __init__(self, shell: Shell): 33 | 34 | super().__init__(shell=shell) 35 | 36 | contents = DemoListBoxScreen.makeContents(client=self, backButton=self.backButton) 37 | self.add_centered(contents) 38 | self.backButton.focus() 39 | 40 | @classmethod 41 | def makeContents(cls, client: Widget, backButton: Button = None) -> Column: 42 | 43 | labelFont = ResourceUtility.get_font(DemoListBoxScreen.DEMO_LABEL_TEXT_SIZE, Theme.BUILT_IN_FONT) 44 | 45 | demoListBoxLabel: Label = Label(text="Pick a good guy", font=labelFont) 46 | 47 | demoListBox: ListBox = ListBox(nrows=3, theClient=client, theItems=DemoListBoxScreen.DEMO_LIST_DATA, selectAction=cls.selectAction) 48 | 49 | cls.selectedLabel: Label = Label(text="No selection") 50 | lbColumnAttrs = { 51 | "align": "c", 52 | 'expand': 0 53 | } 54 | listBoxColumn = Column([demoListBoxLabel, demoListBox], **lbColumnAttrs) 55 | 56 | columnAttrs = { 57 | "align": "l", 58 | 'expand': 0 59 | } 60 | if backButton is None: 61 | contents = Column([listBoxColumn, cls.selectedLabel], **columnAttrs) 62 | else: 63 | contents = Column([listBoxColumn, 64 | cls.selectedLabel, 65 | backButton], **columnAttrs) 66 | return contents 67 | 68 | @classmethod 69 | def selectAction(cls, theSelectedItem: str): 70 | 71 | cls.logger.info("Selected item: %s", theSelectedItem) 72 | cls.selectedLabel.set_text(theSelectedItem) 73 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoMenuBarScreen.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from albow.core.ui.Shell import Shell 5 | 6 | from albow.layout.Column import Column 7 | from albow.layout.Frame import Frame 8 | 9 | from albow.menu.MenuBar import MenuBar 10 | from albow.menu.Menu import Menu 11 | from albow.menu.MenuItem import MenuItem 12 | 13 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 14 | 15 | 16 | class DemoMenuBarScreen(BaseDemoScreen): 17 | 18 | def __init__(self, shell: Shell): 19 | 20 | self.logger = logging.getLogger(__name__) 21 | 22 | super().__init__(shell=shell) 23 | 24 | items = [ 25 | MenuItem(text="Item 1", command="menuItem1"), 26 | MenuItem(text="Item 2", command="menuItem2"), 27 | MenuItem(text="Item 3", command="menuItem3"), 28 | MenuItem(text="Item 4", command="menuItem4") 29 | ] 30 | fileMenu = Menu(title="File", items=items) 31 | editMenu = Menu(title="Edit", items=items) 32 | viewMenu = Menu(title="View", items=items) 33 | helpMenu = Menu(title="Help", items=items) 34 | menus = [ 35 | fileMenu, editMenu, viewMenu, helpMenu 36 | ] 37 | 38 | menuBar = MenuBar(menus=menus, width=self.width/2) 39 | 40 | framedMenuBar = Frame(client=menuBar) 41 | columnAttrs = { 42 | "align": "l", 43 | 'expand': 0 44 | } 45 | contents = Column([framedMenuBar, self.backButton], **columnAttrs) 46 | 47 | self.add_centered(contents) 48 | self.backButton.focus() 49 | 50 | def menuItem1_cmd(self): 51 | self.logger.info("Executed menu item 1 command") 52 | 53 | def menuItem2_cmd(self): 54 | self.logger.info("Executed menu item 2 command") 55 | 56 | def menuItem3_cmd(self): 57 | self.logger.info("Executed menu item 3 command") 58 | 59 | def menuItem4_cmd(self): 60 | self.logger.info("Executed menu item 4 command") 61 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoMultiChoiceScreen.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import pygame 4 | 5 | import logging 6 | from logging import Logger 7 | 8 | from albow.core.ui.Shell import Shell 9 | from albow.core.ResourceUtility import ResourceUtility 10 | 11 | from albow.widgets.Label import Label 12 | from albow.widgets.Button import Button 13 | 14 | from albow.choices.TextMultiChoice import TextMultiChoice 15 | from albow.choices.ImageMultiChoice import ImageMultiChoice 16 | 17 | from albow.layout.Column import Column 18 | from albow.layout.Row import Row 19 | 20 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 21 | 22 | IMAGE_RESOURCES_SUBDIR = "images" 23 | DEMO_CHOICE_IMAGE_1 = "EnterpriseD.png" 24 | DEMO_CHOICE_IMAGE_2 = "KlingonD7.png" 25 | DEMO_CHOICE_IMAGE_3 = "medfighter.png" 26 | 27 | DEMO_IMAGES = [ 28 | DEMO_CHOICE_IMAGE_1, 29 | DEMO_CHOICE_IMAGE_2, 30 | DEMO_CHOICE_IMAGE_3 31 | ] 32 | DEMO_IMAGE_VALUES = [ 33 | "Enterprise D", 34 | "Klingon D7", 35 | "Med Fighter" 36 | ] 37 | 38 | 39 | class DemoMultiChoiceScreen(BaseDemoScreen): 40 | 41 | classLogger: Logger = logging.getLogger(__name__) 42 | 43 | def __init__(self, shell: Shell): 44 | 45 | super().__init__(shell=shell) 46 | 47 | contents = DemoMultiChoiceScreen.makeContents(backButton=self.backButton) 48 | self.add_centered(contents) 49 | self.backButton.focus() 50 | 51 | @classmethod 52 | def makeContents(cls, backButton: Button = None) -> Column: 53 | 54 | textLabel = Label("Make a choice: ") 55 | textMultiChoice = cls.makeTextMultiChoice() 56 | 57 | imageLabel = Label("Pick your ship: ") 58 | imageMultiChoice = cls.makeImageMultiChoice() 59 | 60 | rowAttrs = { 61 | 'spacing': 2 62 | } 63 | textRow = Row([textLabel, textMultiChoice], **rowAttrs) 64 | imageRow = Row([imageLabel, imageMultiChoice], **rowAttrs) 65 | 66 | innerColumnAttrs = { 67 | "align": "l" 68 | } 69 | innerColumn: Column = Column([textRow, imageRow], spacing=10, **innerColumnAttrs) 70 | 71 | columnAttrs = { 72 | "align": "c", 73 | "margin": 5, 74 | 'border_width': 1 75 | } 76 | if backButton is None: 77 | # contents = Column([innerColumn, self.backButton], spacing=10, **columnAttrs) 78 | contents = innerColumn 79 | else: 80 | contents = Column([innerColumn, backButton], spacing=10, **columnAttrs) 81 | 82 | return contents 83 | 84 | @classmethod 85 | def makeTextMultiChoice(cls): 86 | 87 | textValues = ["Value 1", "Value 2", "Value 3"] 88 | labelValues = ["Choice 1", "Choice 2", "Choice 3"] 89 | textMultiChoice = TextMultiChoice(values=textValues, labels=labelValues) 90 | 91 | return textMultiChoice 92 | 93 | @classmethod 94 | def makeImageMultiChoice(cls): 95 | 96 | cls.classLogger.debug(f"Resource directory: {ResourceUtility.find_resource_dir()}") 97 | 98 | pathToImages = ResourceUtility.find_resource_dir() + "/" + IMAGE_RESOURCES_SUBDIR 99 | 100 | cls.classLogger.debug(f"Path to images: {pathToImages}") 101 | 102 | choiceImages = [] 103 | for imageFileName in DEMO_IMAGES: 104 | 105 | imagePath = os.path.join(pathToImages, imageFileName) 106 | 107 | cls.classLogger.debug(f"Image Path: {imagePath}") 108 | choiceImage = pygame.image.load(imagePath) 109 | choiceImages.append(choiceImage) 110 | 111 | imageMultiChoice = ImageMultiChoice(images=choiceImages, values=DEMO_IMAGE_VALUES) 112 | 113 | return imageMultiChoice 114 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoMusicScreen.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from albow.core.ui.Shell import Shell 5 | 6 | from albow.layout.Column import Column 7 | 8 | from albow.widgets.Button import Button 9 | 10 | from albow.dialog.DialogUtilities import alert 11 | 12 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 13 | 14 | from albow.media.MusicOptionsDialog import MusicOptionsDialog 15 | from albow.media.PlayList import PlayList 16 | 17 | from albow.media.MusicUtilities import MusicUtilities 18 | 19 | 20 | class DemoMusicScreen(BaseDemoScreen): 21 | 22 | def __init__(self, shell: Shell): 23 | 24 | self.logger = logging.getLogger(__name__) 25 | 26 | super().__init__(shell=shell) 27 | 28 | columnAttrs = { 29 | "align": "c", 30 | 'expand': 0 31 | } 32 | attrs = { 33 | 'font': self.smallButtonFont 34 | } 35 | launchMusicDialogButt: Button = Button(text="Options Dialog", action=DemoMusicScreen.testOptionsDialog, **attrs) 36 | loadDemoMusicButt: Button = Button(text="Load Music", action=DemoMusicScreen.testLoadMusic, **attrs) 37 | playMusicButt: Button = Button(text="Play Music", action=DemoMusicScreen.playMusic, **attrs) 38 | stopMusicButt: Button = Button(text="Stop Music", action=DemoMusicScreen.stopMusic, **attrs) 39 | 40 | contents = Column([launchMusicDialogButt, 41 | loadDemoMusicButt, 42 | playMusicButt, 43 | stopMusicButt, 44 | self.backButton], **columnAttrs) 45 | self.add_centered(contents) 46 | self.backButton.focus() 47 | 48 | @staticmethod 49 | def testOptionsDialog(): 50 | 51 | dialog: MusicOptionsDialog = MusicOptionsDialog() 52 | 53 | dialog.present() 54 | 55 | @staticmethod 56 | def testLoadMusic(): 57 | 58 | path1 = MusicUtilities.get_music("ElecPiK04 75E-01.mp3") 59 | path2 = MusicUtilities.get_music("ElecPiK04 75E-02.mp3") 60 | path3 = MusicUtilities.get_music("ElecPiK04 75E-03.mp3") 61 | path4 = MusicUtilities.get_music("ElecPiK04 75E-04.mp3") 62 | 63 | MusicUtilities.set_music_enabled(False) 64 | paths = {path1, path2, path3, path4} 65 | playList = PlayList(items=paths, random=True, repeat=True) 66 | MusicUtilities.change_playlist(new_playlist=playList) 67 | 68 | alert("Music Loaded") 69 | 70 | @staticmethod 71 | def playMusic(): 72 | 73 | if MusicUtilities.get_current_playlist() is None: 74 | 75 | alert("Demo music not loaded. Loading my favorite track") 76 | 77 | favPath = MusicUtilities.get_music("Zoe_Poledouris_-_I_Have_Not_Been_To_Paradise_David_Bowie_Cover.mp3") 78 | paths = {favPath} 79 | favPlayList = PlayList(items=paths, random=True, repeat=True) 80 | favPlayList.repeat = False 81 | favPlayList.random = False 82 | MusicUtilities.change_playlist(new_playlist=favPlayList) 83 | 84 | else: 85 | MusicUtilities.set_music_enabled(True) 86 | MusicUtilities.start_next_music() 87 | 88 | @staticmethod 89 | def stopMusic(): 90 | MusicUtilities.music_end() 91 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoPaletteViewScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.layout.Column import Column 5 | 6 | from albow.widgets.Label import Label 7 | 8 | from albow.demo.views.DemoPaletteView import DemoPaletteView 9 | 10 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 11 | 12 | 13 | class DemoPaletteViewScreen(BaseDemoScreen): 14 | 15 | def __init__(self, shell: Shell): 16 | 17 | # 18 | # Python 3 update 19 | # 20 | super().__init__(shell) 21 | 22 | w, h = self.size # Extract from tuple 23 | 24 | grid = DemoPaletteView() 25 | lbl = Label("Cl1ck a Squ4r3") 26 | 27 | grid.border_width = 1 28 | grid.center = (w/2, h/2) 29 | grid.output = lbl 30 | columnAttrs = { 31 | "align": "c", 32 | 'expand': 0 33 | } 34 | 35 | contents = Column([grid, lbl, self.backButton], **columnAttrs) 36 | self.add_centered(contents) 37 | 38 | def go_back(self): 39 | self.parent.show_menu() 40 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoTabPanelScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.event import Event 3 | 4 | from albow.containers.TabPanel import TabPanel 5 | from albow.core.ui.Widget import Widget 6 | from albow.core.ui.Shell import Shell 7 | 8 | from albow.widgets.Label import Label 9 | 10 | from albow.layout.Column import Column 11 | 12 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 13 | 14 | 15 | class DemoTabPanelScreen(BaseDemoScreen): 16 | """ 17 | 18 | """ 19 | def __init__(self, shell: Shell): 20 | """ 21 | 22 | :param shell: 23 | """ 24 | super().__init__(shell) 25 | tabPanel = TabPanel(enterTabAction=self.enterTabAction, exitTabAction=self.exitTabAction) 26 | tabPanel.size = 400, 200 27 | self.pages = tabPanel 28 | 29 | for i in range(1, 4): 30 | page = self.make_test_page(i) 31 | tabPanel.add_page("Page %s" % i, page) 32 | 33 | contents = Column([tabPanel, self.backButton], spacing=BaseDemoScreen.DEFAULT_CONTENT_SPACING) 34 | self.add_centered(contents) 35 | 36 | def make_test_page(self, pageNumber: int) -> Widget: 37 | """ 38 | 39 | :param pageNumber: Guess :-) 40 | 41 | :return: The widget page 42 | """ 43 | page_size = self.pages.content_size() 44 | page = Widget(size=page_size, border_width=1) 45 | lbl = Label(f"This is page {pageNumber}") 46 | page.add_centered(lbl) 47 | 48 | return page 49 | 50 | def enterTabAction(self, theEvent: Event): 51 | self.logger.info(f"enterTabAction - index: {theEvent.index}") 52 | 53 | def exitTabAction(self, theEvent: Event): 54 | self.logger.info(f"extiTabAction - index: {theEvent.index}") -------------------------------------------------------------------------------- /albow/demo/screens/DemoTableScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.widgets.Label import Label 3 | 4 | from albow.layout.Column import Column 5 | 6 | from albow.demo.views.DemoTableView import DemoTableView 7 | 8 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 9 | 10 | 11 | class DemoTableScreen(BaseDemoScreen): 12 | """ 13 | Table View 14 | """ 15 | 16 | def __init__(self, shell): 17 | """ 18 | 19 | :param shell: 20 | """ 21 | super().__init__(shell) 22 | 23 | title = Label("Norwegian Butter Exports", font=self.labelFont) 24 | table: DemoTableView = DemoTableView() 25 | 26 | contents = Column([title, table, self.backButton], spacing=BaseDemoScreen.DEFAULT_CONTENT_SPACING) 27 | 28 | self.add_centered(contents) 29 | -------------------------------------------------------------------------------- /albow/demo/screens/DemoTextFieldsScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Shell import Shell 3 | 4 | from albow.widgets.Button import Button 5 | from albow.widgets.Label import Label 6 | 7 | from albow.input.TextField import TextField 8 | 9 | from albow.widgets.TextBox import TextBox 10 | from albow.widgets.CheckBox import CheckBox 11 | 12 | from albow.layout.Column import Column 13 | from albow.layout.Row import Row 14 | 15 | from albow.layout.Grid import Grid 16 | 17 | from albow.demo.screens.BaseDemoScreen import BaseDemoScreen 18 | 19 | 20 | class DemoTextFieldsScreen(BaseDemoScreen): 21 | """ 22 | Text Field 23 | """ 24 | nameField: TextField 25 | raceField: TextField 26 | resultLabel: Label 27 | textBox: TextBox 28 | lineCtr: int = 0 29 | 30 | def __init__(self, shell: Shell): 31 | """ 32 | 33 | Args: 34 | shell: Our parent shell 35 | """ 36 | super().__init__(shell) 37 | 38 | contents = DemoTextFieldsScreen.makeContents(self.backButton) 39 | self.add_centered(contents) 40 | 41 | @classmethod 42 | def ok(cls): 43 | cls.resultLabel.text = "You are a %s called %s." % (cls.raceField.text, cls.nameField.text) 44 | 45 | @classmethod 46 | def appendText(cls): 47 | 48 | cls.lineCtr += 1 49 | line: str = f"Line {cls.lineCtr}{TextBox.LINE_SEPARATOR}" 50 | oldLines: str = cls.textBox.getText() 51 | 52 | oldLines += line 53 | cls.textBox.setText(oldLines) 54 | 55 | @classmethod 56 | def insertText(cls): 57 | cls.lineCtr += 1 58 | line: str = f'Line {cls.lineCtr}' 59 | cls.textBox.insertText(theNewLine=line) 60 | 61 | @classmethod 62 | def clearText(cls): 63 | 64 | cls.textBox.clearText() 65 | cls.lineCtr = 0 66 | 67 | @classmethod 68 | def deleteText(cls): 69 | cls.textBox.deleteText(2) 70 | 71 | @classmethod 72 | def makeTextBoxTesterContainer(cls) -> Row: 73 | 74 | cls.textBox = TextBox(theNumberOfColumns=20, theNumberOfRows=10) 75 | 76 | checkBoxRow: Row = Row([CheckBox(), Label('Last Line Visible')]) 77 | 78 | appendTextButton: Button = Button('Append', action=cls.appendText) 79 | insertTextButton: Button = Button('Insert', action=cls.insertText) 80 | deleteTextButton: Button = Button('Delete', action=cls.deleteText) 81 | clearTextButton: Button = Button('Clear ', action=cls.clearText) 82 | 83 | contentAttrs = { 84 | "align": "l" 85 | } 86 | buttHolder: Column = Column([appendTextButton, insertTextButton, deleteTextButton, clearTextButton], **contentAttrs) 87 | textBoxControlHolder: Column = Column([checkBoxRow, buttHolder], **contentAttrs) 88 | 89 | container: Row = Row([cls.textBox, textBoxControlHolder]) 90 | 91 | return container 92 | 93 | @classmethod 94 | def makeContents(cls, backButton: Button = None) -> Column: 95 | 96 | nameLabel: Label = Label("Name: ") 97 | raceLabel: Label = Label("Race: ") 98 | 99 | cls.nameField: TextField = TextField(width=150) 100 | cls.raceField: TextField = TextField(width=150) 101 | 102 | rows = [ 103 | [nameLabel, cls.nameField], 104 | [raceLabel, cls.raceField] 105 | ] 106 | fieldGrid: Grid = Grid(rows) 107 | 108 | cls.resultLabel = Label("") 109 | cls.resultLabel.width = 400 110 | 111 | minMaxAttrs = {'min': 50, 'max': 240} 112 | minMaxField: TextField = TextField(**minMaxAttrs) 113 | minMaxField.type = int 114 | minMaxField.set_text("222") 115 | 116 | okBtn = Button("OK", action=cls.ok) 117 | 118 | tbTestContainer = cls.makeTextBoxTesterContainer() 119 | contentAttrs = { 120 | "align": "c" 121 | } 122 | 123 | if backButton is None: 124 | contents: Column = Column([fieldGrid, cls.resultLabel, okBtn, minMaxField, tbTestContainer], **contentAttrs) 125 | else: 126 | contents: Column = Column([fieldGrid, cls.resultLabel, okBtn, minMaxField, tbTestContainer, backButton], **contentAttrs) 127 | 128 | return contents 129 | -------------------------------------------------------------------------------- /albow/demo/screens/GridDebugScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import cast 3 | 4 | import logging 5 | 6 | from albow.core.ResourceUtility import ResourceUtility 7 | from albow.core.ui.Screen import Screen 8 | from albow.core.ui.Shell import Shell 9 | 10 | from albow.widgets.Button import Button 11 | from albow.widgets.Label import Label 12 | 13 | from albow.layout.Column import Column 14 | from albow.layout.Grid import Grid 15 | 16 | from albow.themes.Theme import Theme 17 | 18 | DEMO_TITLE_TEXT_SIZE = 24 19 | DEMO_BUTTON_TEXT_SIZE = 12 20 | 21 | 22 | class GridDebugScreen(Screen): 23 | """ 24 | Buttons 25 | """ 26 | 27 | def __init__(self, shell: Shell): 28 | """ 29 | 30 | :param shell: 31 | """ 32 | self.logger = logging.getLogger(__name__) 33 | # 34 | # Python 3 update 35 | # 36 | # Screen.__init__(self, shell) 37 | super().__init__(shell) 38 | 39 | from albow.demo.DemoShell import DemoShell 40 | self.shell = cast(DemoShell, shell) 41 | f1 = ResourceUtility.get_font(DEMO_TITLE_TEXT_SIZE, Theme.BUILT_IN_FONT) 42 | 43 | title = Label("Albow Demonstration", font=f1) 44 | # emptyButton = Button("Empty", enabled=False) 45 | 46 | menuArray = [ 47 | [ 48 | self.screen_button("R0C0"), 49 | self.screen_button("R0C1"), 50 | self.screen_button("R0C2"), 51 | ], 52 | # [ 53 | # self.screen_button("R1C0"), 54 | # self.screen_button("R1Column1"), 55 | # self.screen_button("R1C2"), 56 | # ], 57 | # [ 58 | # self.screen_button("R2C0"), 59 | # self.screen_button("R2Column1"), 60 | # self.screen_button("R2C2"), 61 | # ], 62 | # [ 63 | # self.screen_button("R3C0"), 64 | # self.screen_button("R3Column1"), 65 | # self.screen_button("R3C2") 66 | # ], 67 | # [ 68 | # self.screen_button("R4C0"), 69 | # self.screen_button("R4Column1"), 70 | # self.screen_button("R4C2") 71 | # ] 72 | ] 73 | 74 | menuGrid = Grid(rows=menuArray, column_spacing=10, row_spacing=2, margin=5) 75 | quitButton = Button("Quit", shell.quit) 76 | 77 | self.equallySizeButtons(menuArray) 78 | 79 | contents = Column([ 80 | title, 81 | menuGrid, 82 | quitButton 83 | ], align='c', spacing=10, border_width=1, border_color=Theme.BLUE, margin=10) 84 | self.add_centered(contents) 85 | 86 | def screen_button(self, text: str): 87 | 88 | retButton = Button(text) 89 | return retButton 90 | 91 | def equallySizeButtons(self, menuArray): 92 | 93 | largestWidth: int = 0 94 | for buttRow in menuArray: 95 | for butt in buttRow: 96 | self.logger.debug("Button text: %s, width: %s", butt.text, butt.width) 97 | currWidth = butt.width 98 | if currWidth > largestWidth: 99 | largestWidth = currWidth 100 | 101 | self.logger.debug("largestWidth: %s", largestWidth) 102 | 103 | for buttRow in menuArray: 104 | for butt in buttRow: 105 | butt.width = largestWidth 106 | 107 | return menuArray 108 | 109 | def __repr__(self): 110 | return self.__class__.__name__ 111 | -------------------------------------------------------------------------------- /albow/demo/screens/LaunchDemosScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import cast 3 | 4 | import logging 5 | 6 | from albow.core.ResourceUtility import ResourceUtility 7 | from albow.core.ui.Screen import Screen 8 | from albow.core.ui.Shell import Shell 9 | 10 | from albow.widgets.Button import Button 11 | from albow.widgets.Label import Label 12 | 13 | from albow.layout.Column import Column 14 | from albow.layout.Grid import Grid 15 | 16 | from albow.themes.Theme import Theme 17 | 18 | DEMO_TITLE_TEXT_SIZE = 24 19 | DEMO_BUTTON_TEXT_SIZE = 12 20 | 21 | 22 | class LaunchDemosScreen(Screen): 23 | """ 24 | Buttons 25 | """ 26 | 27 | def __init__(self, shell: Shell): 28 | """ 29 | 30 | :param shell: 31 | """ 32 | self.logger = logging.getLogger(__name__) 33 | # 34 | # Python 3 update 35 | # 36 | # Screen.__init__(self, shell) 37 | super().__init__(shell) 38 | 39 | from albow.demo.DemoShell import DemoShell 40 | self.shell = cast(DemoShell, shell) 41 | f1 = ResourceUtility.get_font(DEMO_TITLE_TEXT_SIZE, Theme.BUILT_IN_FONT) 42 | 43 | title = Label("Albow Demonstration", font=f1) 44 | # emptyButton = Button("Empty", enabled=False) 45 | 46 | menuArray = [ 47 | [ 48 | self.screen_button("Text Screen", self.shell.text_screen), 49 | self.screen_button("Text Fields", self.shell.fields_screen), 50 | self.screen_button("Controls", self.shell.controls_screen), 51 | ], 52 | [ 53 | self.screen_button("Animation", self.shell.anim_screen), 54 | self.screen_button("Grid View", self.shell.grid_screen), 55 | self.screen_button("Palette View", self.shell.palette_screen), 56 | ], 57 | [ 58 | self.screen_button("Image Array", self.shell.image_array_screen), 59 | self.screen_button("Modal Dialogs", self.shell.dialog_screen), 60 | self.screen_button("Tab Panel", self.shell.tab_panel_screen), 61 | ], 62 | [ 63 | self.screen_button("Table View", self.shell.table_screen), 64 | self.screen_button("MultiChoice", self.shell.multiChoiceScreen), 65 | self.screen_button("MenuBar", self.shell.menuBarScreen) 66 | ], 67 | [ 68 | self.screen_button("Music", self.shell.musicScreen), 69 | self.screen_button("ListBox", self.shell.listBoxScreen), 70 | self.screen_button("User Events", self.shell.userEventsScreen) 71 | ] 72 | ] 73 | 74 | menuGrid = Grid(rows=menuArray, column_spacing=5, row_spacing=2, margin=5) 75 | quitButton = Button("Quit", shell.quit) 76 | 77 | self.equallySizeButtons(menuArray) 78 | 79 | contents = Column([ 80 | title, 81 | menuGrid, 82 | quitButton 83 | ], align='c', spacing=10) 84 | self.add_centered(contents) 85 | 86 | def screen_button(self, text: str, screen: Screen): 87 | 88 | # buttFont = ResourceUtility.get_font(DEMO_BUTTON_TEXT_SIZE, Theme.BUILT_IN_FONT) 89 | # buttAttrs = { 90 | # 'font': buttFont 91 | # } 92 | retButton = Button(text, action=lambda: self.shell.show_screen(screen)) 93 | return retButton 94 | 95 | def equallySizeButtons(self, menuArray): 96 | 97 | largestWidth: int = 0 98 | for buttRow in menuArray: 99 | for butt in buttRow: 100 | self.logger.debug("Button text: %s, width: %s", butt.text, butt.width) 101 | currWidth = butt.width 102 | if currWidth > largestWidth: 103 | largestWidth = currWidth 104 | 105 | self.logger.debug("largestWidth: %s", largestWidth) 106 | 107 | for buttRow in menuArray: 108 | for butt in buttRow: 109 | butt.width = largestWidth 110 | 111 | return menuArray 112 | 113 | def __repr__(self): 114 | return self.__class__.__name__ 115 | -------------------------------------------------------------------------------- /albow/demo/screens/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the demonstration screen classes 3 | """ -------------------------------------------------------------------------------- /albow/demo/views/DemoGridView.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.color import Color 3 | 4 | from albow.themes.Theme import Theme 5 | from albow.containers.GridView import GridView 6 | 7 | DEMO_NCOLS = 3 8 | DEMO_NROWS = 2 9 | DEMO_CELL_WIDTH = 60 10 | DEMO_CELL_HEIGHT = 40 11 | DEMO_NUMBER_OF_COLUMNS = 3 12 | DEMO_NUMBER_OF_ROWS = 2 13 | 14 | 15 | class DemoGridView(GridView): 16 | """ 17 | Grid View 18 | """ 19 | 20 | info = [ 21 | [("red", "r3d"), ("green", "gr33n"), ("blue", "blu3")], 22 | [("cyan", "cy4n"), ("magenta", "m4g3nt4"), ("yellow", "y3ll0w")] 23 | ] 24 | 25 | def __init__(self): 26 | """ 27 | """ 28 | super().__init__((DEMO_CELL_WIDTH, DEMO_CELL_HEIGHT), DEMO_NROWS, DEMO_NCOLS) 29 | self.output = None 30 | 31 | def num_rows(self): 32 | return DEMO_NUMBER_OF_ROWS 33 | 34 | def num_cols(self): 35 | return DEMO_NUMBER_OF_COLUMNS 36 | 37 | def draw_cell(self, surface, row, col, rect): 38 | color = Color(self.info[row][col][0]) 39 | surface.fill(color, rect) 40 | 41 | def click_cell(self, row, col, event): 42 | self.output.text = self.info[row][col][1] 43 | -------------------------------------------------------------------------------- /albow/demo/views/DemoPaletteView.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from pygame.color import Color 4 | 5 | from albow.containers.PaletteView import PaletteView 6 | 7 | 8 | class DemoPaletteView(PaletteView): 9 | """ 10 | Palette View 11 | """ 12 | 13 | info = ["red", "green", "blue", "cyan", "magenta", "yellow"] 14 | 15 | sel_color = Color("white") 16 | sel_width = 5 17 | 18 | def __init__(self): 19 | 20 | super().__init__((60, 60), 2, 2, scrolling=True) 21 | self.selection = None 22 | self.border_width = 1 23 | 24 | def num_items(self): 25 | return len(self.info) 26 | 27 | def draw_item(self, surface, item_no, rect): 28 | 29 | inflationSize = -2 * self.sel_width 30 | r = rect.inflate(inflationSize, inflationSize) 31 | color = Color(self.info[item_no]) 32 | surface.fill(color, r) 33 | 34 | def click_item(self, item_no, theEvent): 35 | self.selection = item_no 36 | 37 | def item_is_selected(self, item_no): 38 | return self.selection == item_no 39 | -------------------------------------------------------------------------------- /albow/demo/views/DemoTableView.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.event import Event 3 | 4 | from albow.table.TableView import TableView 5 | from albow.table.TableColumn import TableColumn 6 | 7 | 8 | class DemoTableView(TableView): 9 | 10 | demo_table_data = [ 11 | (1979, 12.5), 12 | (1980, 13.2), 13 | (1981, 13.5), 14 | (1982, 13.1), 15 | (1983, 14.3), 16 | (1984, 15.4), 17 | (1985, 16.4), 18 | (1986, 17.4), 19 | (1987, 18.4), 20 | (1988, 19.4), 21 | (2019, 23.0) 22 | ] 23 | 24 | selected_table_row = None 25 | 26 | columns = [ 27 | TableColumn("Year", 70), 28 | TableColumn("Amount", 50, 'r', "%.1f"), 29 | ] 30 | 31 | def num_rows(self): 32 | return len(self.demo_table_data) 33 | 34 | def row_data(self, i): 35 | return self.demo_table_data[i] 36 | 37 | def row_is_selected(self, i, theEvent: Event = None) -> bool: 38 | return self.selected_table_row == i 39 | 40 | def click_row(self, i, e): 41 | self.selected_table_row = i 42 | -------------------------------------------------------------------------------- /albow/demo/views/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | This package contains 'View' base widgets 3 | """ -------------------------------------------------------------------------------- /albow/dialog/Dialog.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.dialog.Modal import Modal 3 | from albow.widgets.Button import Button 4 | 5 | from albow.core.ui.Widget import Widget 6 | from albow.layout.Row import Row 7 | from albow.layout.Column import Column 8 | 9 | 10 | class Dialog(Modal, Widget): 11 | """ 12 | The Dialog class provides a convenient container for implementing modal dialogs. Pressing Return or 13 | Enter dismisses the dialog with the value True, and pressing Escape dismisses it with the value False. 14 | 15 | See the `albow.core.ui.Widget` ``dismiss()`` and ``present()`` methods 16 | """ 17 | click_outside_response = None 18 | """ 19 | If this attribute is given a non-None value, then a mouse-down event outside the bounds of the dialog will 20 | cause it to be dismissed with the given value. 21 | """ 22 | def __init__(self, client=None, responses=None, default=0, cancel=-1, **kwds): 23 | """ 24 | 25 | Args: 26 | client: The widget the dialog is on top of 27 | 28 | responses: A list of responses 29 | 30 | default: The index to the default response; Default is the first 31 | 32 | cancel: The index to the cancel response; Default is None 33 | 34 | **kwds: 35 | """ 36 | 37 | Widget.__init__(self, **kwds) 38 | if client or responses: 39 | rows = [] 40 | w1 = 0 41 | w2 = 0 42 | if client: 43 | rows.append(client) 44 | w1 = client.width 45 | if responses: 46 | buttons = Row([ 47 | Button(text, action=lambda t=text: self.dismiss(t)) 48 | for text in responses], equalize='w') 49 | rows.append(buttons) 50 | w2 = buttons.width 51 | if w1 < w2: 52 | a = 'l' 53 | else: 54 | a = 'r' 55 | contents = Column(rows, align=a) 56 | m = self.margin 57 | contents.topleft = (m, m) 58 | self.add(contents) 59 | self.shrink_wrap() 60 | 61 | if responses and default is not None: 62 | self.enter_response = responses[default] 63 | if responses and cancel is not None: 64 | self.cancel_response = responses[cancel] 65 | 66 | def mouse_down(self, e): 67 | # 68 | # PEP 8 update 69 | # https://lintlyci.github.io/Flake8Rules/rules/E713.html 70 | # 71 | # if not e in self: 72 | if e not in self: 73 | response = self.click_outside_response 74 | if response is not None: 75 | self.dismiss(response) 76 | -------------------------------------------------------------------------------- /albow/dialog/DialogTitleBar.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Surface 3 | from pygame import Rect 4 | 5 | from logging import Logger 6 | from logging import getLogger 7 | 8 | from albow.core.ui.Widget import Widget 9 | 10 | from albow.themes.Theme import Theme 11 | 12 | 13 | class DialogTitleBar(Widget): 14 | 15 | Y_OFFSET: int = 2 16 | X_OFFSET: int = 5 17 | 18 | def __init__(self, theTitle: str = 'Default Title', **kwds): 19 | 20 | self.title: str = theTitle 21 | 22 | super().__init__(**kwds) 23 | 24 | self.height: int = self.height // 5 25 | self.border_width = 1 26 | self.logger: Logger = getLogger(__name__) 27 | 28 | def draw(self, theSurface: Surface): 29 | """ 30 | Args: 31 | theSurface: The surface onto which to draw 32 | """ 33 | w: int = self._rect.width # syntactic sugar 34 | h: int = self._rect.height # syntactic sugar 35 | 36 | self.logger.debug(f'w: {w} h: {h} margin: {self.margin}') 37 | 38 | tBar: Rect = Rect((0, 0), (w, h)) 39 | self.logger.debug(f'tBar: {tBar} theSurface: {theSurface}') 40 | theSurface.fill(Theme.LAMAS_OFF_WHITE, tBar) 41 | 42 | def draw_over(self, theSurface: Surface): 43 | """ 44 | 45 | Args: 46 | theSurface: The surface onto which to draw 47 | """ 48 | buf = self.font.render(self.title, True, self.fg_color) 49 | theSurface.blit(buf, (DialogTitleBar.X_OFFSET, DialogTitleBar.Y_OFFSET)) 50 | -------------------------------------------------------------------------------- /albow/dialog/DirectoryPathView.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Surface 3 | 4 | from albow.core.ui.Widget import Widget 5 | 6 | 7 | class DirectoryPathView(Widget): 8 | 9 | def __init__(self, width, client, **kwds): 10 | 11 | super().__init__(**kwds) 12 | self.set_size_for_text(width) 13 | self.client = client 14 | 15 | def draw(self, surface: Surface): 16 | 17 | frame = self.get_margin_rect() 18 | image = self.font.render(self.client.directory, True, self.fg_color) 19 | tw = image.get_width() 20 | mw = frame.width 21 | if tw <= mw: 22 | x = 0 23 | else: 24 | x = mw - tw 25 | surface.blit(image, (frame.left + x, frame.top)) 26 | -------------------------------------------------------------------------------- /albow/dialog/FileDialog.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from albow.themes.ThemeProperty import ThemeProperty 5 | 6 | from albow.dialog.Dialog import Dialog 7 | from albow.dialog.DirectoryPathView import DirectoryPathView 8 | from albow.dialog.FileListView import FileListView 9 | 10 | from albow.widgets.Label import Label 11 | from albow.widgets.Button import Button 12 | 13 | from albow.layout.Row import Row 14 | from albow.layout.Column import Column 15 | from albow.input.TextField import TextField 16 | 17 | 18 | class FileDialog(Dialog): 19 | 20 | box_width = 250 21 | default_prompt = None 22 | up_button_text = ThemeProperty("up_button_text") 23 | 24 | def __init__(self, prompt=None, suffixes=None, **kwds): 25 | 26 | super().__init__(**kwds) 27 | 28 | label = None 29 | d = self.margin 30 | self.suffixes = suffixes or () 31 | if self.up_button_text is None: 32 | self.up_button_text = '' 33 | 34 | up_button = Button(self.up_button_text, action=self.go_up) 35 | dir_box = DirectoryPathView(self.box_width - up_button.width - 10, self) 36 | self.dir_box = dir_box 37 | top_row = Row([dir_box, up_button]) 38 | list_box = FileListView(self.box_width - 16, self) 39 | self.list_box = list_box 40 | ctrls = [top_row, list_box] 41 | prompt = prompt or self.default_prompt 42 | 43 | if prompt: 44 | label = Label(prompt) 45 | if self.saving: 46 | filename_box = TextField(self.box_width) 47 | filename_box.change_action = self.update 48 | self.filename_box = filename_box 49 | ctrls.append(Column([label, filename_box], align='l', spacing=0)) 50 | else: 51 | if label: 52 | ctrls.insert(0, label) 53 | 54 | ok_button = Button(self.ok_label, action=self.ok, enable=self.ok_enable) 55 | self.ok_button = ok_button 56 | cancel_button = Button("Cancel", action=self.cancel) 57 | vbox = Column(ctrls, align='l', spacing=d) 58 | vbox.topleft = (d, d) 59 | y = vbox.bottom + d 60 | ok_button.topleft = (vbox.left, y) 61 | cancel_button.topright = (vbox.right, y) 62 | self.add(vbox) 63 | self.add(ok_button) 64 | self.add(cancel_button) 65 | self.shrink_wrap() 66 | self._directory = None 67 | self.directory = os.getcwd() 68 | # print "FileDialog: cwd =", repr(self.directory) ### 69 | if self.saving: 70 | filename_box.focus() 71 | 72 | def get_directory(self): 73 | return self._directory 74 | 75 | def set_directory(self, x): 76 | x = os.path.abspath(x) 77 | while not os.path.exists(x): 78 | y = os.path.dirname(x) 79 | if y == x: 80 | x = os.getcwd() 81 | break 82 | x = y 83 | # if self._directory <> x: 84 | if self._directory != x: 85 | self._directory = x 86 | self.list_box.update() 87 | self.update() 88 | 89 | directory = property(get_directory, set_directory) 90 | 91 | def filter(self, path): 92 | suffixes = self.suffixes 93 | if not suffixes: 94 | return os.path.isfile(path) 95 | for suffix in suffixes: 96 | if path.endswith(suffix): 97 | return True 98 | 99 | def update(self): 100 | pass 101 | 102 | def go_up(self): 103 | self.directory = os.path.dirname(self.directory) 104 | 105 | def dir_box_click(self, double): 106 | if double: 107 | name = self.list_box.get_selected_name() 108 | path = os.path.join(self.directory, name) 109 | suffix = os.path.splitext(name)[1] 110 | if suffix not in self.suffixes and os.path.isdir(path): 111 | self.directory = path 112 | else: 113 | self.double_click_file(name) 114 | self.update() 115 | 116 | def ok(self): 117 | self.dismiss(True) 118 | 119 | def cancel(self): 120 | self.dismiss(False) 121 | -------------------------------------------------------------------------------- /albow/dialog/FileDialogUtilities.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from albow.dialog.FileOpenDialog import FileOpenDialog 5 | from albow.dialog.LookForFileDialog import LookForFileDialog 6 | from albow.dialog.FileSaveDialog import FileSaveDialog 7 | 8 | 9 | def request_new_filename(prompt=None, suffix=None, extra_suffixes=None, directory=None, filename=None, pathname=None): 10 | """ 11 | Presents a dialog for specifying a new file. 12 | 13 | Args: 14 | prompt: The prompt is displayed as a prompt to the user. 15 | 16 | suffix: The suffix, if any, will be appended to the filename returned if necessary, and also 17 | specifies which files can be chosen. 18 | 19 | extra_suffixes: The extra_suffixes can be a list of additional suffixes of choosable files. 20 | 21 | directory: Specifies the starting directory 22 | 23 | filename: The initial contents of the filename box 24 | 25 | pathname: Alternatively the pathname can be used to specify both of these together 26 | 27 | Returns: The full pathname of the file chosen, or None if the dialog is cancelled. 28 | 29 | """ 30 | if pathname: 31 | directory, filename = os.path.split(pathname) 32 | if extra_suffixes: 33 | suffixes = extra_suffixes 34 | else: 35 | suffixes = [] 36 | if suffix: 37 | suffixes = [suffix] + suffixes 38 | dlog = FileSaveDialog(prompt=prompt, suffixes=suffixes) 39 | if directory: 40 | dlog.directory = directory 41 | if filename: 42 | dlog.filename = filename 43 | if dlog.present(): 44 | return dlog.pathname 45 | else: 46 | return None 47 | 48 | 49 | def request_old_filename(suffixes=None, directory=None): 50 | """ 51 | Presents a dialog for choosing an existing file. 52 | 53 | Args: 54 | suffixes: The suffixes is a list of filename extensions defining 55 | the files that can be chosen; if not specified, any file can be chosen. 56 | 57 | directory: The directory specifies the directory 58 | in which to start browsing; if unspecified, the current working directory is used. 59 | 60 | Returns: Returns the full pathname of the file chosen, or None if the dialog is cancelled. 61 | """ 62 | attrs = {'margin': 10} 63 | 64 | dlog = FileOpenDialog(suffixes=suffixes, **attrs) 65 | if directory: 66 | dlog.directory = directory 67 | if dlog.present(): 68 | return dlog.pathname 69 | else: 70 | return None 71 | 72 | 73 | def look_for_file_or_directory(target, prompt=None, directory=None): 74 | """ 75 | This function is used to ask the user to locate a file or directory with a specific name 76 | Any directory can be visited, but only files whose last pathname component matches the target are visible 77 | and choosable. 78 | 79 | Args: 80 | target: The file/directory to look for 81 | 82 | prompt: The prompt to display 83 | 84 | directory: Specifies the directory in which to start browsing. 85 | 86 | Returns: Returns the full pathname of the file or directory chosen, or None if the dialog is cancelled. 87 | """ 88 | 89 | dlog = LookForFileDialog(target=target, prompt=prompt) 90 | if directory: 91 | dlog.directory = directory 92 | if dlog.present(): 93 | return dlog.pathname 94 | else: 95 | return None 96 | -------------------------------------------------------------------------------- /albow/dialog/FileListView.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from pygame import Surface 5 | from pygame import Rect 6 | 7 | from albow.containers.PaletteView import PaletteView 8 | from albow.dialog.DialogUtilities import alert 9 | 10 | 11 | class FileListView(PaletteView): 12 | 13 | def __init__(self, width, client, **kwds): 14 | """ 15 | 16 | :param width: 17 | :param client: 18 | :param kwds: 19 | """ 20 | font = self.predict_font(kwds) 21 | h = font.get_linesize() 22 | d = 2 * self.predict(kwds, 'margin') 23 | super().__init__((width - d, h), 10, 1, scrolling=True, **kwds) 24 | self.client = client 25 | self.selection = None 26 | self.names = [] 27 | 28 | def update(self): 29 | client = self.client 30 | directory = client.directory 31 | 32 | def aFilter(name): 33 | path = os.path.join(directory, name) 34 | return os.path.isdir(path) or self.client.filter(path) 35 | 36 | try: 37 | names = [name for name in os.listdir(directory) 38 | if not name.startswith(".") and aFilter(name)] 39 | except EnvironmentError as e: 40 | alert("%s: %s" % (directory, e)) 41 | names = [] 42 | self.names = names 43 | self.selection = None 44 | 45 | def num_items(self): 46 | return len(self.names) 47 | 48 | def draw_item(self, surface: Surface, item_no: int, rect: Rect): 49 | 50 | color = self.fg_color 51 | buf = self.font.render(self.names[item_no], True, color) 52 | surface.blit(buf, rect) 53 | 54 | def click_item(self, item_no, e): 55 | self.selection = item_no 56 | self.client.dir_box_click(e.num_clicks == 2) 57 | 58 | def item_is_selected(self, item_no): 59 | return item_no == self.selection 60 | 61 | def get_selected_name(self): 62 | sel = self.selection 63 | if sel is not None: 64 | return self.names[sel] 65 | else: 66 | return "" 67 | 68 | -------------------------------------------------------------------------------- /albow/dialog/FileOpenDialog.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from albow.dialog.FileDialog import FileDialog 5 | 6 | 7 | class FileOpenDialog(FileDialog): 8 | 9 | saving = False 10 | ok_label = "Open" 11 | 12 | def get_pathname(self): 13 | name = self.list_box.get_selected_name() 14 | if name: 15 | return os.path.join(self.directory, name) 16 | else: 17 | return None 18 | 19 | pathname = property(get_pathname) 20 | 21 | def ok_enable(self): 22 | path = self.pathname 23 | enabled = self.item_is_choosable(path) 24 | return enabled 25 | 26 | def item_is_choosable(self, path): 27 | return bool(path) and self.filter(path) 28 | 29 | def double_click_file(self, name): 30 | self.ok() 31 | -------------------------------------------------------------------------------- /albow/dialog/FileSaveDialog.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from albow.dialog.DialogUtilities import ask 5 | from albow.dialog.FileDialog import FileDialog 6 | 7 | 8 | class FileSaveDialog(FileDialog): 9 | 10 | saving = True 11 | default_prompt = "Save as:" 12 | ok_label = "Save" 13 | 14 | def get_filename(self): 15 | return self.filename_box.value 16 | 17 | def set_filename(self, x): 18 | dsuf = self.suffixes[0] 19 | if x.endswith(dsuf): 20 | x = x[:-len(dsuf)] 21 | self.filename_box.value = x 22 | 23 | filename = property(get_filename, set_filename) 24 | 25 | def get_pathname(self): 26 | path = os.path.join(self.directory, self.filename_box.value) 27 | suffixes = self.suffixes 28 | if suffixes and not path.endswith(suffixes[0]): 29 | path = path + suffixes[0] 30 | return path 31 | 32 | pathname = property(get_pathname) 33 | 34 | def double_click_file(self, name): 35 | self.filename_box.value = name 36 | 37 | def ok(self): 38 | path = self.pathname 39 | if os.path.exists(path): 40 | answer = ask("Replace existing '%s'?" % os.path.basename(path)) 41 | # 42 | # Python 3 update 43 | # if answer <> "OK": 44 | if answer != "OK": 45 | return 46 | FileDialog.ok(self) 47 | 48 | def update(self): 49 | FileDialog.update(self) 50 | 51 | def ok_enable(self): 52 | 53 | # 54 | # Python 3 update 55 | # 56 | # return self.filename_box.text <> "" 57 | return self.filename_box.text != "" 58 | -------------------------------------------------------------------------------- /albow/dialog/LookForFileDialog.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from albow.dialog.FileOpenDialog import FileOpenDialog 5 | 6 | 7 | class LookForFileDialog(FileOpenDialog): 8 | 9 | target = None 10 | 11 | def __init__(self, target, **kwds): 12 | 13 | super().__init__(**kwds) 14 | self.target = target 15 | 16 | def item_is_choosable(self, path): 17 | return path and os.path.basename(path) == self.target 18 | 19 | def filter(self, name): 20 | return name and os.path.basename(name) == self.target 21 | -------------------------------------------------------------------------------- /albow/dialog/Modal.py: -------------------------------------------------------------------------------- 1 | 2 | class Modal: 3 | """ 4 | Modal is a mixin class for use by widgets that define modal states. It provides default responses for the Enter 5 | and Escape keys and methods that can be used as actions for OK and Cancel buttons. 6 | """ 7 | enter_response = True 8 | """ 9 | Value with which to dismiss a modal dialog when the Return or Enter key is pressed. 10 | """ 11 | cancel_response = False 12 | """ 13 | Value with which to dismiss a modal dialog when the Escape key is pressed. 14 | """ 15 | 16 | # These aren't used because subclasses of Modal seem to be using 17 | # Widget.dismiss() 18 | # 19 | # def ok(self): 20 | # self.dismiss(True) 21 | # 22 | # def cancel(self): 23 | # self.dismiss(False) 24 | -------------------------------------------------------------------------------- /albow/dialog/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the base dialog classes as well as the 'File' dialogs 3 | """ -------------------------------------------------------------------------------- /albow/input/FloatField.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.input.Field import Field 3 | 4 | 5 | class FloatField(Field): 6 | 7 | """ 8 | A control for editing values of type float. 9 | """ 10 | def __init__(self, width=None, **kwds): 11 | self.type = float 12 | super().__init__(width, **kwds) 13 | -------------------------------------------------------------------------------- /albow/input/IntField.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.input.Field import Field 3 | 4 | 5 | class IntField(Field): 6 | 7 | """ 8 | A control for editing values of type int. 9 | 10 | """ 11 | def __init__(self, width=None, **kwds): 12 | self.type = int 13 | super().__init__(width, **kwds) 14 | -------------------------------------------------------------------------------- /albow/input/TextField.py: -------------------------------------------------------------------------------- 1 | from albow.input.Field import Field 2 | 3 | 4 | class TextField(Field): 5 | 6 | """ 7 | A control for editing values of type str. 8 | 9 | """ 10 | _value = "" 11 | 12 | def __init__(self, width=None, **kwds): 13 | self.type = str 14 | super().__init__(width, **kwds) 15 | -------------------------------------------------------------------------------- /albow/input/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | This package contains the basic text input widgets. It supports integer and float input 3 | """ -------------------------------------------------------------------------------- /albow/layout/Column.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List 3 | 4 | from albow.core.ui.Widget import Widget 5 | from albow.layout.RowOrColumn import RowOrColumn 6 | 7 | 8 | class Column(RowOrColumn): 9 | """ 10 | A Column is a container widget that arranges its contents in a vertical column. In an 11 | OpenGL window, it may contain 3D subwidgets. 12 | 13 | .. Note:: 14 | The layout is only performed when the widget is initially created; it is not updated if you add or remove 15 | widgets later or change their sizes. 16 | """ 17 | def __init__(self, items: List[Widget], height=None, **kwds): 18 | """ 19 | 20 | Specify the following as keyword: value pairs in kwds 21 | 22 | - align: 23 | The widgets in items are added as subwidgets and arranged vertically with spacing pixels between. 24 | Horizontal alignment is controlled by align, which is one of 'l', 'c' or 'r' for left, center or right. 25 | 26 | - spacing: The spacing between the widgets in pixels 27 | 28 | - equalize: 29 | If equalize contains 'w', the widths of all the items are made equal to the widest one, and 'lr' is 30 | added to their anchor properties. If equalize contains 'h', the heights of all the items are 31 | made equal to the tallest one. 32 | 33 | - expand: The index of the widget in the items list 34 | 35 | 36 | Args: 37 | items: The widgets to add as items 38 | 39 | height: If a height is specified, then expand may be a widget or an index into the items, and the specified 40 | widget has its height adjusted to fill the remaining space. Otherwise, the initial size of the 41 | Column is calculated from its contents. 42 | 43 | **kwds: 44 | """ 45 | self.d = (0, 1) 46 | self.minor_axis = 'w' 47 | self.axis = 'v' 48 | self.longways = 'height' 49 | self.crossways = 'width' 50 | self.align_map = { 51 | 'l': (0, 'topleft', 'bottomleft'), 52 | 'c': (1, 'midtop', 'midbottom'), 53 | 'r': (2, 'topright', 'bottomright'), 54 | } 55 | 56 | super().__init__(height, items, kwds) 57 | 58 | def __repr__(self): 59 | return self.__class__.__name__ 60 | -------------------------------------------------------------------------------- /albow/layout/Frame.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Widget import Widget 3 | 4 | 5 | class Frame(Widget): 6 | """ 7 | A Frame is a container widget that adds a border around a client widget at a specified distance. 8 | """ 9 | 10 | border_width: int = 1 11 | """ 12 | The width of the border 13 | """ 14 | margin: int = 2 15 | """ 16 | The spacing between border and widget 17 | """ 18 | 19 | def __init__(self, client: Widget, border_spacing: int = None, **kwds): 20 | """ 21 | 22 | Args: 23 | client: The widget to wrap 24 | 25 | border_spacing: Distance between the edges of the client and the border line. 26 | 27 | **kwds: 28 | """ 29 | super().__init__(**kwds) 30 | 31 | self.client = client 32 | if border_spacing is not None: 33 | self.margin = self.border_width + border_spacing 34 | d = self.margin 35 | w, h = client.size 36 | self.size = (w + 2 * d, h + 2 * d) 37 | client.topleft = (d, d) 38 | self.add(client) 39 | -------------------------------------------------------------------------------- /albow/layout/Grid.py: -------------------------------------------------------------------------------- 1 | 2 | from logging import Logger 3 | from logging import getLogger 4 | 5 | from pygame import Rect 6 | 7 | from albow.core.ui.Widget import Widget 8 | 9 | 10 | DEFAULT_GRID_COLUMN_SPACING = 10 11 | DEFAULT_GRID_ROW_SPACING = 10 12 | DEFAULT_GRID_MARGIN = 3 13 | 14 | 15 | class Grid(Widget): 16 | 17 | _is_gl_container = True 18 | margin = 0 19 | 20 | def __init__(self, rows, row_spacing=DEFAULT_GRID_ROW_SPACING, column_spacing=DEFAULT_GRID_COLUMN_SPACING, **kwds): 21 | 22 | self.logger: Logger = getLogger(__name__) 23 | 24 | self.margin = m = kwds.pop('margin', self.margin) 25 | if self.margin == 0: 26 | self.margin = DEFAULT_GRID_MARGIN 27 | m = DEFAULT_GRID_MARGIN 28 | 29 | self.logger.debug(f'margin: {self.margin}') 30 | col_widths = [0] * len(rows[0]) 31 | row_heights = [0] * len(rows) 32 | for j, row in enumerate(rows): 33 | for i, widget in enumerate(row): 34 | if widget: 35 | col_widths[i] = max(col_widths[i], widget.width) 36 | row_heights[j] = max(row_heights[j], widget.height) 37 | 38 | self.logger.debug(f"column_spacing: {column_spacing}") 39 | self.logger.debug(f"... col_widths: {col_widths} ... row_heights: {row_heights}") 40 | 41 | row_top: int = 0 42 | col_left: int = 0 43 | for j, row in enumerate(rows): 44 | h = row_heights[j] 45 | y = m + row_top + h // 2 46 | 47 | col_left = 0 48 | for i, widget in enumerate(row): 49 | w = col_widths[i] 50 | if widget: 51 | if i == 0: 52 | x = m + col_left 53 | self.logger.debug(f"x: {x} m: {m} + col_left: {col_left}") 54 | else: 55 | x = column_spacing + col_left 56 | self.logger.debug(f"x: {x} column_spacing: {column_spacing} + col_left: {col_left}") 57 | 58 | widget.midleft = (x, y) 59 | self.logger.debug(f'widget.midleft: {widget.midleft}') 60 | col_left += w + column_spacing 61 | row_top += h + row_spacing 62 | # 63 | # Bad warning on "local variable col_left might be referenced before assignment" 64 | # 65 | width = max(1, col_left - column_spacing) 66 | height = max(1, row_top - row_spacing) 67 | m2 = 2 * m 68 | self.logger.debug(f"width: '{width}' m2: '{m2}' column_spacing: '{column_spacing}' height: '{height}' #cols: {len(col_widths)}") 69 | realWidth: int = width + m2 + (column_spacing * (len(col_widths) - 1)) 70 | r = Rect(0, 0, realWidth, height + m2) 71 | 72 | self.logger.debug(f"r = {r}") 73 | 74 | super().__init__(r, **kwds) 75 | self.add(rows) 76 | 77 | def __repr__(self): 78 | return self.__class__.__name__ 79 | -------------------------------------------------------------------------------- /albow/layout/Row.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List 3 | 4 | from albow.core.ui.Widget import Widget 5 | 6 | from albow.layout.RowOrColumn import RowOrColumn 7 | 8 | 9 | class Row(RowOrColumn): 10 | """ 11 | A Row is a container widget that arranges its contents in a horizontal row. In an OpenGL window, 12 | it may contain 3D subwidgets. 13 | 14 | .. Note:: 15 | The layout is only performed when the widget is initially created; it is not updated if you add or remove 16 | widgets later or change their sizes. 17 | 18 | """ 19 | def __init__(self, items: List[Widget], width=None, **kwds): 20 | """ 21 | 22 | Args: 23 | items: The widgets to add as items 24 | 25 | width: If a width is specified, then expand may be a widget or an index into the items, and the 26 | specified widget has its width adjusted to fill the remaining space. Otherwise, the initial size 27 | of the Row is calculated from its contents. 28 | 29 | **kwds: 30 | """ 31 | self.d = (1, 0) 32 | self.minor_axis = 'h' 33 | self.axis = 'h' 34 | self.longways = 'width' 35 | self.crossways = 'height' 36 | self.align_map = { 37 | 't': (0, 'topleft', 'topright'), 38 | 'c': (1, 'midleft', 'midright'), 39 | 'b': (2, 'bottomleft', 'bottomright'), 40 | } 41 | 42 | super().__init__(width, items, kwds) 43 | -------------------------------------------------------------------------------- /albow/layout/RowOrColumn.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Widget import Widget 3 | 4 | 5 | class RowOrColumn(Widget): 6 | """ 7 | 8 | """ 9 | axis_anchors = {'w': 'lr', 'h': 'tb'} 10 | 11 | _is_gl_container = True 12 | 13 | d: tuple = None 14 | minor_axis: str = None 15 | axis: str = None 16 | longways: str = None 17 | crossways: str = None 18 | align_map: dict = None 19 | 20 | def __init__(self, size, items, kwds): 21 | """ 22 | 23 | Args: 24 | size: 25 | items: 26 | kwds: 27 | """ 28 | align = kwds.pop('align', 'c') 29 | spacing = kwds.pop('spacing', 10) 30 | expand = kwds.pop('expand', None) 31 | equalize = kwds.pop('equalize', '') 32 | if isinstance(expand, int): 33 | expand = items[expand] 34 | self.equalize_widgets(items, equalize) 35 | # 36 | # Python 3 update 37 | # Widget.__init__(self, **kwds) 38 | super().__init__(**kwds) 39 | 40 | d = self.d 41 | longways = self.longways 42 | crossways = self.crossways 43 | axis = self.axis 44 | k, attr2, attr3 = self.align_map[align] 45 | w = 0 46 | length = 0 47 | if isinstance(expand, int): 48 | expand = items[expand] 49 | move = '' 50 | for item in items: 51 | r = item.rect 52 | w = max(w, getattr(r, crossways)) 53 | if item is expand: 54 | item.set_resizing(axis, 's') 55 | move = 'm' 56 | else: 57 | item.set_resizing(axis, move) 58 | length += getattr(r, longways) 59 | if size is not None: 60 | n = len(items) 61 | if n > 1: 62 | length += spacing * (n - 1) 63 | setattr(expand.rect, longways, max(1, size - length)) 64 | h = w * k // 2 65 | m = self.margin 66 | px = h * d[1] + m 67 | py = h * d[0] + m 68 | sx = spacing * d[0] 69 | sy = spacing * d[1] 70 | for item in items: 71 | setattr(item.rect, attr2, (px, py)) 72 | self.add(item) 73 | p = getattr(item.rect, attr3) 74 | px = p[0] + sx 75 | py = p[1] + sy 76 | self.shrink_wrap() 77 | 78 | def equalize_widgets(self, items, mode): 79 | if items: 80 | if 'w' in mode: 81 | s = max(w.width for w in items) 82 | for w in items: 83 | w.width = s 84 | if 'h' in mode: 85 | s = max(w.height for w in items) 86 | for w in items: 87 | w.height = s 88 | if self.minor_axis in mode: 89 | anchor = self.axis_anchors[self.minor_axis] 90 | for w in items: 91 | w.add_anchor(anchor) 92 | -------------------------------------------------------------------------------- /albow/layout/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | this package contains the main and support classes for the layout widgets 3 | """ -------------------------------------------------------------------------------- /albow/media/EnableMusicControl.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.widgets.CheckBox import CheckBox 3 | 4 | from albow.media.MusicUtilities import MusicUtilities 5 | 6 | 7 | class EnableMusicControl(CheckBox): 8 | """ 9 | A control for enabling and disabling the playing of music by the `albow.media.MusicUtilities` module. 10 | """ 11 | def get_value(self): 12 | return MusicUtilities.get_music_enabled() 13 | 14 | def set_value(self, newState: bool): 15 | MusicUtilities.set_music_enabled(newState) 16 | -------------------------------------------------------------------------------- /albow/media/MusicOptionsDialog.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.layout.Column import Column 3 | from albow.layout.Grid import Grid 4 | 5 | from albow.dialog.Dialog import Dialog 6 | from albow.widgets.Label import Label 7 | from albow.widgets.Button import Button 8 | 9 | from albow.media.EnableMusicControl import EnableMusicControl 10 | from albow.media.MusicVolumeControl import MusicVolumeControl 11 | 12 | 13 | class MusicOptionsDialog(Dialog): 14 | """ 15 | A simple dialog for controlling music-playing options. Incorporates an 16 | `albow.media.EnableMusicControl` and an `albow.media.MusicVolumeControl`. 17 | """ 18 | def __init__(self): 19 | 20 | # 21 | # Python 3 update 22 | # 23 | super().__init__() 24 | emc = EnableMusicControl() 25 | mvc = MusicVolumeControl() 26 | controls = Grid([ 27 | [Label("Enable Music"), emc], 28 | [Label("Music Volume"), mvc], 29 | ]) 30 | buttons = Button("OK", self.ok) 31 | contents = Column([controls, buttons], align='r', spacing=20) 32 | contents.topleft = (20, 20) 33 | self.add(contents) 34 | self.shrink_wrap() 35 | 36 | def ok(self): 37 | self.dismiss(True) -------------------------------------------------------------------------------- /albow/media/MusicVolumeControl.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | from pygame import Surface 4 | 5 | from pygame.event import Event 6 | 7 | from albow.core.ui.Widget import Widget 8 | 9 | try: 10 | from pygame.mixer import music 11 | except ImportError: 12 | music = None 13 | print("Music not available") 14 | 15 | if music: 16 | # 17 | # Pygame 1.9 update 18 | # music.set_endevent(MUSIC_END_EVENT) 19 | # This needs updating 20 | music.set_endevent() 21 | 22 | 23 | class MusicVolumeControl(Widget): 24 | """ 25 | A control for adjusting the volume of music played by the music module. 26 | """ 27 | def __init__(self, **kwds): 28 | # 29 | # Python 3 update 30 | # 31 | super().__init__(Rect((0, 0), (100, 20)), **kwds) 32 | 33 | def draw(self, surf: Surface): 34 | 35 | r = self.get_margin_rect() 36 | r.width = int(round(music.get_volume() * r.width)) 37 | surf.fill(self.fg_color, r) 38 | 39 | def mouse_down(self, e: Event): 40 | self.mouse_drag(e) 41 | 42 | def mouse_drag(self, e: Event): 43 | 44 | m = self.margin 45 | w = self.width - 2 * m 46 | x = max(0.0, min(1.0, (e.local[0] - m) / w)) 47 | music.set_volume(x) 48 | self.invalidate() 49 | -------------------------------------------------------------------------------- /albow/media/PlayList.py: -------------------------------------------------------------------------------- 1 | 2 | from random import randrange 3 | 4 | 5 | class PlayList: 6 | """ 7 | A collection of music filenames to be played sequentially or 8 | randomly. If random is true, items will be played in a random order. 9 | If repeat is true, the list will be repeated indefinitely, otherwise 10 | each item will only be played once. 11 | """ 12 | 13 | random: bool = None 14 | """ 15 | If true, items will be played in a random order. Otherwise, they will be played in the order they were supplied 16 | to the constructor. 17 | """ 18 | repeat: bool = None 19 | """ 20 | If true, the list will be repeated indefinitely. Otherwise, each item will only be played once. 21 | """ 22 | 23 | def __init__(self, items, random: bool=False, repeat: bool=False): 24 | """ 25 | Constructs a PlayList from a list of music file pathnames. 26 | 27 | Args: 28 | items: Music file pathnames 29 | 30 | random: The new random state 31 | 32 | repeat: The new repeat state 33 | """ 34 | self.items = list(items) 35 | self.random = random 36 | self.repeat = repeat 37 | 38 | def next(self): 39 | """ 40 | Returns the next item to be played. 41 | """ 42 | items = self.items 43 | if items: 44 | if self.random: 45 | n = len(items) 46 | if self.repeat: 47 | n = (n + 1) // 2 48 | i = randrange(n) 49 | else: 50 | i = 0 51 | item = items.pop(i) 52 | if self.repeat: 53 | items.append(item) 54 | return item 55 | -------------------------------------------------------------------------------- /albow/media/Sound.py: -------------------------------------------------------------------------------- 1 | """ 2 | The sound module exports utility functions related to sound. These functions have the property that 3 | if sound support is not available, they do nothing, allowing an application to continue without sound. 4 | """ 5 | import pygame 6 | from pygame import mixer 7 | 8 | 9 | def pause_sound(): 10 | """ 11 | Pauses all sound channels. Equivalent to `pygame.mixer.pause()`. 12 | """ 13 | try: 14 | mixer.pause() 15 | except pygame.error: 16 | pass 17 | 18 | 19 | def resume_sound(): 20 | """ 21 | Resumes channels previously paused by `pause_sound()`. Equivalent to `pygame.mixer.unpause().` 22 | """ 23 | try: 24 | mixer.unpause() 25 | except pygame.error: 26 | pass 27 | 28 | 29 | def stop_sound(): 30 | """ 31 | Stops all sound channels. Equivalent to `pygame.mixer.stop()`. 32 | """ 33 | try: 34 | mixer.stop() 35 | except pygame.error: 36 | pass 37 | -------------------------------------------------------------------------------- /albow/media/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The media module provides some facilities for handling music. Individual pieces of music can be 3 | 4 | - started 5 | - stopped 6 | 7 | `PlayLists` can be 8 | 9 | - constructed 10 | - played sequentially 11 | - played randomly. 12 | 13 | There are also some control and dialog classes available for setting music-related options. 14 | """ -------------------------------------------------------------------------------- /albow/menu/MenuItem.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | 5 | class MenuItem: 6 | 7 | keyname = "" 8 | keycode = None 9 | shift = False 10 | alt = False 11 | enabled = False 12 | 13 | if sys.platform.startswith('darwin') or sys.platform.startswith('mac'): 14 | cmd_name = "Cmd " 15 | option_name = "Opt " 16 | else: 17 | cmd_name = "Ctrl " 18 | option_name = "Alt " 19 | 20 | def __init__(self, text="", command=None): 21 | 22 | self.command = command 23 | if "/" in text: 24 | text, key = text.split("/", 1) 25 | else: 26 | key = "" 27 | self.text = text 28 | if key: 29 | keyname = key[-1] 30 | mods = key[:-1] 31 | self.keycode = ord(keyname.lower()) 32 | if "^" in mods: 33 | self.shift = True 34 | keyname = "Shift " + keyname 35 | if "@" in mods: 36 | self.alt = True 37 | keyname = self.option_name + keyname 38 | self.keyname = self.cmd_name + keyname 39 | -------------------------------------------------------------------------------- /albow/menu/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the main and support classes for the Menu widgets 3 | """ -------------------------------------------------------------------------------- /albow/openGL/GLOrtho.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | 4 | # noinspection PyPackageRequirements 5 | from OpenGL import GL 6 | 7 | from albow.openGL.GLViewport import GLViewport 8 | 9 | 10 | class GLOrtho(GLViewport): 11 | """ 12 | GLOrtho provides an OpenGL drawing area with an orthographic projection. 13 | 14 | Using a GLOrtho widget is the same as using a GLViewport, except that you do not need to 15 | provide a `setup_projection()` method. 16 | 17 | ------ 18 | 19 | ------ 20 | 21 | """ 22 | def __init__(self, rect: Rect=None, xmin=-1, xmax=1, ymin=-1, ymax=1, near=-1, far=1, **kwds): 23 | """ 24 | Creates a GLOrtho instance with the given initial values for its projection parameters. 25 | 26 | Args: 27 | rect: A pygame Rect 28 | 29 | xmin: Specify the coordinates for the left vertical clipping planes. 30 | 31 | xmax: Specify the coordinates for the right vertical clipping planes. 32 | 33 | ymin: Specify the coordinates for the bottom horizontal clipping planes. 34 | 35 | ymax: Specify the coordinates for the top horizontal clipping planes. 36 | 37 | near: Specify the distances to the nearer clipping planes. 38 | These distances are negative if the plane is to be behind the viewer. 39 | 40 | far: Specify the distances to the depth clipping planes. 41 | These distances are negative if the plane is to be behind the viewer. 42 | 43 | **kwds: 44 | """ 45 | # 46 | # Python 3 update 47 | # 48 | # GLViewport.__init__(self, rect, **kwds) 49 | super().__init__(rect, **kwds) 50 | 51 | self.xmin = xmin 52 | self.xmax = xmax 53 | self.ymin = ymin 54 | self.ymax = ymax 55 | self.near = near 56 | self.far = far 57 | 58 | def setup_projection(self): 59 | GL.glOrtho(self.xmin, self.xmax, self.ymin, self.ymax, self.near, self.far) 60 | -------------------------------------------------------------------------------- /albow/openGL/GLPerspective.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | 4 | # noinspection PyPackageRequirements 5 | from OpenGL import GLU 6 | 7 | from albow.openGL.GLViewport import GLViewport 8 | 9 | 10 | class GLPerspective(GLViewport): 11 | """ 12 | `GLPerspective` provides an OpenGL drawing area with a perspective projection. 13 | 14 | Using a GLPerspective widget is the same as using a `GLViewport`, except that you do not need to provide 15 | a `setup_projection()` method. 16 | """ 17 | def __init__(self, rect: Rect=None, fovy: int=20, near: float=0.1, far: int=1000, **kwds): 18 | """ 19 | Creates a GLPerspective instance with the given initial values for its projection parameters. 20 | 21 | The projection parameters, as used by `gluPerspective()`. You can change these to dynamically modify 22 | the projection. The aspect ratio is calculated automatically from the widget dimensions. 23 | 24 | Args: 25 | rect: 26 | 27 | fovy: 28 | 29 | near: 30 | 31 | far: 32 | 33 | **kwds: 34 | """ 35 | # 36 | # Python 3 update 37 | # 38 | # GLViewport.__init__(self, rect, **kwds) 39 | super().__init__(rect, **kwds) 40 | 41 | self.fovy = fovy 42 | self.near = near 43 | self.far = far 44 | 45 | def setup_projection(self): 46 | aspect = self.width / self.height 47 | GLU.gluPerspective(self.fovy, aspect, self.near, self.far) 48 | -------------------------------------------------------------------------------- /albow/openGL/GLSurface.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | from pygame import image 4 | 5 | # noinspection PyPackageRequirements 6 | from OpenGL import GL 7 | # noinspection PyPackageRequirements 8 | from OpenGL import GLU 9 | 10 | 11 | class GLSurface: 12 | 13 | def __init__(self, display, rect): 14 | self.display = display 15 | self.rect = rect 16 | 17 | def gl_enter(self): 18 | r = self.rect 19 | w = r.width 20 | h = r.height 21 | gl = GL 22 | win_height = self.display.get_height() 23 | gl.glViewport(r.left, win_height - r.bottom, r.width, r.height) 24 | gl.glMatrixMode(gl.GL_PROJECTION) 25 | gl.glLoadIdentity() 26 | GLU.gluOrtho2D(0, w, h, 0) 27 | gl.glMatrixMode(gl.GL_MODELVIEW) 28 | gl.glLoadIdentity() 29 | gl.glPushAttrib(gl.GL_COLOR_BUFFER_BIT) 30 | gl.glDisable(gl.GL_DEPTH_TEST) 31 | gl.glEnable(gl.GL_BLEND) 32 | gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) 33 | 34 | def gl_exit(self): 35 | GL.glPopAttrib() 36 | 37 | def gl_clear(self, bg): 38 | if bg: 39 | r = bg[0] / 255.0 40 | g = bg[1] / 255.0 41 | b = bg[2] / 255.0 42 | GL.glClearColor(r, g, b, 0.0) 43 | GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT 44 | | GL.GL_ACCUM_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT) 45 | 46 | def gl_flush(self): 47 | GL.glFlush() 48 | 49 | def get_size(self): 50 | return self.rect.size 51 | 52 | def get_width(self): 53 | return self.rect.width 54 | 55 | def get_height(self): 56 | return self.rect.height 57 | 58 | def get_rect(self): 59 | return Rect(self.rect) 60 | 61 | def blit(self, src, dst=(0, 0), area=None): 62 | 63 | if isinstance(dst, Rect): 64 | dst = dst.topleft 65 | x, y = dst 66 | if area is not None: 67 | area = area.clip(src.get_rect()) 68 | src = src.subsurface(area) 69 | x += area.left 70 | y += area.top 71 | w, h = src.get_size() 72 | data = image.tostring(src, 'RGBA', 1) 73 | 74 | gl = GL 75 | gl.glRasterPos2i(x, y + h) 76 | gl.glDrawPixels(w, h, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, data) 77 | 78 | def fill(self, color, rect=None): 79 | 80 | if rect: 81 | x, y, w, h = rect 82 | else: 83 | x = y = 0 84 | w, h = self.rect.size 85 | gl = GL 86 | gl.glColor4ubv(color) 87 | gl.glBegin(GL.GL_QUADS) 88 | v = GL.glVertex2i 89 | v(x, y) 90 | v(x+w, y) 91 | v(x+w, y+h) 92 | v(x, y+h) 93 | gl.glEnd() 94 | 95 | def subsurface(self, rect): 96 | r = self.rect 97 | subrect = rect.move(r.left, r.top) 98 | return GLSurface(self.display, r.clip(subrect)) 99 | -------------------------------------------------------------------------------- /albow/openGL/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Albow can be used in an OpenGL environment. To do this, create an OpenGL display surface 3 | (using pygame.display.set_mode() with OPENGL in the flags) and pass it to the constructor of your root 4 | widget. You can then add widgets derived from GLViewport to provide 3D drawing areas, as well as ordinary 5 | 2D widgets. 6 | 7 | There are a few things to be aware of when mixing 2D and 3D widgets. When using OpenGL, Albow distinguishes three kinds 8 | of widgets: 9 | 10 | 1. 3D widgets 11 | - [GLViewport](GLViewport) 12 | - [GLOrtho](albow.openGL.GLOrtho) 13 | - [GLPerspective](albow.openGL.GLPerspective) 14 | - Classes derived from them. 15 | 16 | 2. 2D widgets -- most other Albow widgets. These are rendered to a temporary surface which is then transferred 17 | to the screen using `glDrawPixels`. 18 | 19 | 3. Container widgets -- used for laying out other widgets. By default these are Row, Column, Grid and classes 20 | derived from them. The root widget is also considered a container widget. 21 | Container widgets and 3D widgets can have any kind of widget as a subwidget, but 2D widgets can only 22 | contain other 2D widgets. 23 | 24 | You can turn any 2D widget into a container widget by setting its is_gl_container property to true. However, 25 | when you do this, no drawing is performed for the widget itself -- its `bg_color` and `border` properties are 26 | ignored, and its `draw()` method is never called. 27 | 28 | You can also turn a container widget back into an ordinary 2D widget by setting its `is_gl_container` property 29 | to `False`. You might want to do this, for example, to give a Row of buttons a background or border. 30 | 31 | Something to keep in mind is that drawing on 2D surfaces and transferring them to an OpenGL window is usually 32 | much slower than drawing directly with OpenGL. So if you want high performance, try to keep the window area 33 | covered by 2D widgets to a minimum. 34 | 35 | """ -------------------------------------------------------------------------------- /albow/profiling.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # 3 | # Albow - Profiling 4 | # 5 | #----------------------------------------------------------------------------- 6 | 7 | from time import time 8 | 9 | enable = False 10 | stack = [] 11 | t_last_begin_frame = 0.0 12 | t_used = 0.0 13 | indent = " " 14 | label_width = 0 15 | 16 | def start_profiling(frame_time): 17 | global target_frame_time, enable 18 | target_frame_time = frame_time 19 | enable = True 20 | del stack[:] 21 | 22 | def stop_profiling(): 23 | global enable 24 | enable = False 25 | 26 | def begin(label): 27 | if enable: 28 | global label_width 29 | label_width = max(label_width, len(label)) 30 | stack.append([label, time()]) 31 | 32 | def end(label): 33 | if enable: 34 | global t_used 35 | t1 = time() 36 | s = stack 37 | while s: 38 | item = s.pop() 39 | item_label = item[0] 40 | t0 = item[1] 41 | t = t1 - t0 42 | print("%s%8.6f %s" % (indent * (len(s) + 1), t, item_label)) 43 | if not s: 44 | t_used += t 45 | if item_label == label: 46 | break 47 | 48 | def begin_frame(): 49 | if enable: 50 | global t_last_begin_frame, t_used 51 | t0 = t_last_begin_frame 52 | t1 = time() 53 | tf = t1 - t0 54 | tu = t_used 55 | ts = target_frame_time - tu 56 | pf = 100.0 * tf / target_frame_time 57 | ps = 100.0 * ts / target_frame_time 58 | print("%8.6f Frame (%3.0f%%) Used: %8.6f Spare: %8.6f (%3.0f%%)" % ( 59 | tf, pf, tu, ts, ps)) 60 | t_last_begin_frame = t1 61 | t_used = 0.0 62 | -------------------------------------------------------------------------------- /albow/table/TableColumn.py: -------------------------------------------------------------------------------- 1 | 2 | class TableColumn: 3 | 4 | format_string = "%s" 5 | 6 | def __init__(self, title: str, width: int, align: str = 'l', fmt: str = None): 7 | """ 8 | 9 | Args: 10 | title: 11 | width: 12 | align: 13 | fmt: 14 | """ 15 | self.title = title 16 | self.width = width 17 | self.alignment = align 18 | if fmt: 19 | # 20 | # Python 3 everything is unicode -- hasii 21 | # 22 | # if isinstance(fmt, (str, unicode)): 23 | # self.format_string = fmt 24 | # else: 25 | # self.formatter = fmt 26 | self.format_string = fmt 27 | 28 | def format(self, data): 29 | if data is not None: 30 | return self.formatter(data) 31 | else: 32 | return "" 33 | 34 | def formatter(self, data): 35 | return self.format_string % data 36 | -------------------------------------------------------------------------------- /albow/table/TableHeaderView.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.table.TableRowBase import TableRowBase 3 | 4 | 5 | class TableHeaderView(TableRowBase): 6 | 7 | def __init__(self, width, height): 8 | super().__init__((width, height), 1, False) 9 | 10 | def row_data(self, row): 11 | pass 12 | 13 | def draw_table_cell(self, surf, data, cell_rect, column): 14 | self.parent.draw_header_cell(surf, cell_rect, column) 15 | -------------------------------------------------------------------------------- /albow/table/TableRowBase.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | 4 | from albow.containers.PaletteView import PaletteView 5 | 6 | 7 | class TableRowBase(PaletteView): 8 | 9 | def __init__(self, cell_size, nrows, scrolling, **kwds): 10 | """ 11 | 12 | :param cell_size: 13 | :param nrows: 14 | :param scrolling: 15 | """ 16 | # 17 | # Python 3 update 18 | # 19 | # PaletteView.__init__(self, cell_size, nrows, 1, scrolling=scrolling) 20 | super().__init__(cell_size, nrows, 1, scrolling=scrolling, **kwds) 21 | 22 | def num_items(self): 23 | return self.parent.num_rows() 24 | 25 | def draw_item(self, surf, row, row_rect): 26 | table = self.parent 27 | height = row_rect.height 28 | row_data = self.row_data(row) 29 | for i, x, width, column, cell_data in table.column_info(row_data): 30 | cell_rect = Rect(x, row_rect.top, width, height) 31 | self.draw_table_cell(surf, cell_data, cell_rect, column) 32 | 33 | def row_data(self, row): 34 | return self.parent.row_data(row) 35 | 36 | def draw_table_cell(self, surf, data, cell_rect, column): 37 | self.parent.draw_table_cell(surf, data, cell_rect, column) 38 | -------------------------------------------------------------------------------- /albow/table/TableRowView.py: -------------------------------------------------------------------------------- 1 | from pygame import event 2 | 3 | from albow.table.TableRowBase import TableRowBase 4 | 5 | 6 | class TableRowView(TableRowBase): 7 | 8 | highlight_style = 'fill' 9 | vstretch = True 10 | 11 | # 12 | # Python 3 update to make sure scrolling values get passed through 13 | # 14 | def __init__(self, cellSize: tuple, nRows: int, scrolling: bool): 15 | super().__init__(cellSize, nRows, scrolling) 16 | 17 | def item_is_selected(self, n: int): 18 | return self.parent.row_is_selected(n) 19 | 20 | def click_item(self, n: int, e: event): 21 | self.parent.click_row(n, e) 22 | -------------------------------------------------------------------------------- /albow/table/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the classes to support and implement the Table widget 3 | """ -------------------------------------------------------------------------------- /albow/text/Page.py: -------------------------------------------------------------------------------- 1 | 2 | class Page: 3 | 4 | def __init__(self, text_screen, heading, lines): 5 | """ 6 | 7 | Args: 8 | text_screen: 9 | heading: 10 | lines: 11 | """ 12 | self.text_screen = text_screen 13 | self.heading = heading 14 | self.lines = lines 15 | width, height = text_screen.heading_font.size(heading) 16 | for line in lines: 17 | w, h = text_screen.font.size(line) 18 | width = max(width, w) 19 | height += h 20 | self.size = (width, height) 21 | 22 | def draw(self, surface, color, pos): 23 | """ 24 | 25 | Args: 26 | surface: 27 | color: 28 | pos: 29 | 30 | Returns: 31 | 32 | """ 33 | heading_font = self.text_screen.heading_font 34 | text_font = self.text_screen.font 35 | x, y = pos 36 | buf = heading_font.render(self.heading, True, color) 37 | surface.blit(buf, (x, y)) 38 | y += buf.get_rect().height 39 | for line in self.lines: 40 | buf = text_font.render(line, True, color) 41 | surface.blit(buf, (x, y)) 42 | y += buf.get_rect().height 43 | -------------------------------------------------------------------------------- /albow/text/TextScreen.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame.locals import * 3 | from albow.core.ui.Screen import Screen 4 | 5 | from albow.themes.FontProperty import FontProperty 6 | 7 | from albow.widgets.Button import Button 8 | from albow.text.Page import Page 9 | 10 | from albow.core.ResourceUtility import ResourceUtility 11 | 12 | from albow.vectors import add 13 | from albow.vectors import maximum 14 | 15 | 16 | class TextScreen(Screen): 17 | 18 | bg_color = (0, 0, 0) 19 | fg_color = (255, 255, 255) 20 | border = 20 21 | 22 | heading_font = FontProperty('heading_font') 23 | button_font = FontProperty('button_font') 24 | 25 | def __init__(self, shell, filename, **kwds): 26 | """""" 27 | text = ResourceUtility.get_text(filename) 28 | text_pages = text.split("\nPAGE\n") 29 | pages = [] 30 | page_size = (0, 0) 31 | for text_page in text_pages: 32 | lines = text_page.strip().split("\n") 33 | page = Page(self, lines[0], lines[1:]) 34 | pages.append(page) 35 | page_size = maximum(page_size, page.size) 36 | self.pages = pages 37 | bf = self.button_font 38 | b1 = Button("Prev Page", font=bf, action=self.prev_page) 39 | b2 = Button("Menu", font=bf, action=self.go_back) 40 | b3 = Button("Next Page", font=bf, action=self.next_page) 41 | b = self.margin 42 | # page_rect = Rect((b, b), page_size) 43 | width_height = list(map(lambda x: x, page_size)) 44 | 45 | page_rect = Rect((b, b),(width_height[0],width_height[1])) 46 | 47 | gap = (0, 18) 48 | # 49 | # Python 3 update 50 | # 51 | # In Python 3 maps and list are not auto-converted 52 | # 53 | # b1.topleft = add(page_rect.bottomleft, gap) 54 | # b2.midtop = add(page_rect.midbottom, gap) 55 | # b3.topright = add(page_rect.bottomright, gap) 56 | b1.topleft = list(add(page_rect.bottomleft, gap)) 57 | b2.midtop = list(add(page_rect.midbottom, gap)) 58 | b3.topright = list(add(page_rect.bottomright, gap)) 59 | 60 | # Screen.__init__(self, shell, **kwds) 61 | super().__init__(shell, **kwds) 62 | # 63 | # Python 3 update 64 | # 65 | # In Python 3 maps and list are not auto-converted 66 | # 67 | # self.size = add(b3.bottomright, (b, b)) 68 | self.size = list(add(b3.bottomright, (b, b))) 69 | self.add(b1) 70 | self.add(b2) 71 | self.add(b3) 72 | self.prev_button = b1 73 | self.next_button = b3 74 | self.set_current_page(0) 75 | 76 | def draw(self, surface): 77 | b = self.margin 78 | self.pages[self.current_page].draw(surface, self.fg_color, (b, b)) 79 | 80 | def at_first_page(self): 81 | return self.current_page == 0 82 | 83 | def at_last_page(self): 84 | return self.current_page == len(self.pages) - 1 85 | 86 | def set_current_page(self, n): 87 | self.current_page = n 88 | self.prev_button.enabled = not self.at_first_page() 89 | self.next_button.enabled = not self.at_last_page() 90 | 91 | def prev_page(self): 92 | if not self.at_first_page(): 93 | self.set_current_page(self.current_page - 1) 94 | 95 | def next_page(self): 96 | if not self.at_last_page(): 97 | self.set_current_page(self.current_page + 1) 98 | 99 | def go_back(self): 100 | self.parent.show_menu() 101 | -------------------------------------------------------------------------------- /albow/text/__init__.py: -------------------------------------------------------------------------------- 1 | """" 2 | This package supports the display of text 3 | """ -------------------------------------------------------------------------------- /albow/themes/FontProperty.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.themes.Theme import Theme 3 | 4 | from albow.themes.ThemeProperty import ThemeProperty 5 | 6 | 7 | class FontProperty(ThemeProperty): 8 | """ 9 | The FontProperty class is a property descriptor used for defining theme properties whose values are font objects. 10 | 11 | Rather than an actual font object, the corresponding attribute of a Theme object should contain a 12 | tuple (size, filename) specifying the font. The first time the font property is accessed for a 13 | given instance, the FontProperty descriptor loads the font using get_font and caches the resulting font object. 14 | 15 | Example 16 | -------- 17 | ```python 18 | class DramaticScene(Widget): 19 | 20 | villain_gloat_font = FontProperty('villain_gloat_font') 21 | 22 | from albow.themes.Theme import themeRoot 23 | 24 | themeRoot.DramaticScene = theme.Theme('DramaticScene') 25 | themeRoot.DramaticScene.villain_gloat_font = (27, "EvilLaughter.ttf") 26 | ``` 27 | 28 | """ 29 | def __init__(self, name): 30 | """ 31 | Constructs a font property descriptor. 32 | Args: 33 | name: Normally name should be the same as the name being used for the property. 34 | """ 35 | super().__init__(name) 36 | 37 | def get_from_theme(self, cls, name): 38 | return Theme.getThemeRoot().get_font(cls, name) 39 | -------------------------------------------------------------------------------- /albow/themes/ThemeError.py: -------------------------------------------------------------------------------- 1 | 2 | class ThemeError(Exception): 3 | pass 4 | -------------------------------------------------------------------------------- /albow/themes/ThemeProperty.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | import logging 5 | 6 | from albow.themes.Theme import Theme 7 | 8 | 9 | class ThemeProperty: 10 | """ 11 | The ThemeProperty class is a property descriptor used for defining theme properties. 12 | 13 | Example 14 | -------- 15 | ```python 16 | 17 | class Battlefield(Widget): 18 | 19 | phaser_color = ThemeProperty('phaser_color') 20 | 21 | ``` 22 | """ 23 | debug_theme = False 24 | 25 | def __init__(self, name): 26 | """ 27 | Constructs a theme property. The name given is used to derive the name under which the 28 | property value is cached, by pre-pending an underscore. Normally name should be the same as 29 | the name being used for the property. 30 | 31 | Args: 32 | name: The property name 33 | """ 34 | self.logger = logging.getLogger(__name__) 35 | self.name = name 36 | self.cache_name = sys.intern("_" + name) 37 | 38 | def __get__(self, obj, owner): 39 | # self.logger.debug("%s(%s).__get__(%s)", self.__class__.__name__, self.name, obj) 40 | 41 | try: 42 | cache_name = self.cache_name 43 | try: 44 | return getattr(obj, cache_name) 45 | except AttributeError as e: 46 | if ThemeProperty.debug_theme: 47 | self.logger.exception(f"{e}. Ok. Get value from Theme") 48 | value = self.get_from_theme(obj.__class__, self.name) 49 | obj.__dict__[cache_name] = value 50 | return value 51 | except (ValueError, Exception): 52 | if ThemeProperty.debug_theme: 53 | import traceback 54 | traceback.print_exc() 55 | self.logger.debug("-------------------------------------------------------") 56 | raise 57 | 58 | def __set__(self, obj, value): 59 | """ 60 | 61 | Args: 62 | obj: 63 | 64 | value: 65 | 66 | Returns: 67 | 68 | """ 69 | # self.logger.debug(f"Setting {obj}.{self.cache_name} = {value}") 70 | obj.__dict__[self.cache_name] = value 71 | 72 | def get_from_theme(self, cls, name): 73 | return Theme.getThemeRoot().get(cls, name) 74 | -------------------------------------------------------------------------------- /albow/themes/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Themes provide a centralized way of customising the appearance of Albow widgets on a per-class basis. There 3 | are three parts to the theme system: 4 | 5 | - Theme properties, which are attributes that get looked up 6 | automatically in the currently active themes 7 | - The Theme class, instances of which hold values for 8 | the theme properties of a particular class; 9 | - The theme module, which holds a default hierarchy of Theme instances that your application can replace. 10 | 11 | See the documentation pages on each of these for more details. 12 | 13 | """ 14 | __pdoc__ = { 'resources': False} 15 | -------------------------------------------------------------------------------- /albow/themes/resources/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/themes/resources/Vera.ttf -------------------------------------------------------------------------------- /albow/themes/resources/VeraBd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/albow/themes/resources/VeraBd.ttf -------------------------------------------------------------------------------- /albow/themes/resources/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package is for private resources of the default theme. It 3 | is not meant to contain resources for demonstration programs and 4 | is different than the lookup method in `core.ResourceUtility` that 5 | is meant as an aid to developers who are using the Albow APIs 6 | 7 | """ -------------------------------------------------------------------------------- /albow/vectors.py: -------------------------------------------------------------------------------- 1 | try: 2 | 3 | from Numeric import add, subtract, maximum 4 | 5 | except ImportError: 6 | 7 | import operator 8 | 9 | def add(x, y): 10 | return map(operator.add, x, y) 11 | 12 | def subtract(x, y): 13 | return map(operator.sub, x, y) 14 | 15 | def maximum(*args): 16 | result = args[0] 17 | for x in args[1:]: 18 | result = map(max, result, x) 19 | return result 20 | -------------------------------------------------------------------------------- /albow/version.py: -------------------------------------------------------------------------------- 1 | version = '2.80' 2 | -------------------------------------------------------------------------------- /albow/widgets/Button.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from pygame import Surface 5 | from pygame import Rect 6 | from pygame import draw 7 | 8 | from albow.themes.Theme import Theme 9 | 10 | from albow.widgets.ButtonBase import ButtonBase 11 | from albow.widgets.Label import Label 12 | 13 | 14 | class Button(ButtonBase, Label): 15 | """ 16 | A Button is a widget with a textual label which can be clicked with the mouse to trigger an action. 17 | 18 | """ 19 | 20 | def __init__(self, text, action=None, enable=None, **kwds): 21 | 22 | """ 23 | 24 | Args: 25 | text: Initializes the button with the given text. 26 | 27 | action: The action should be a function with no arguments; it is called 28 | when the mouse is clicked and released again inside the button. 29 | 30 | enable: If supplied, enable should be a function 31 | that returns a boolean; it will be used to determine whether the button is enabled. 32 | **kwds: 33 | """ 34 | self.logger = logging.getLogger(__name__) 35 | 36 | if action: 37 | kwds['action'] = action 38 | if enable: 39 | kwds['enable'] = enable 40 | Label.__init__(self, text, **kwds) 41 | self.border_color = Theme.BLACK 42 | # self.border_width = 1 43 | 44 | def draw_with(self, surface: Surface, fg: tuple, bg: tuple = None): 45 | 46 | self.border_width = 0 47 | super().draw_with(surface, fg, bg) 48 | 49 | w = self._rect.width # syntactic sugar 50 | h = self._rect.height # syntactic sugar 51 | 52 | # draw border for normal button 53 | draw.rect(surface, Theme.BLACK, Rect((0, 0, w, h)), 1) # black border around everything 54 | draw.line(surface, Theme.WHITE, (1, 1), (w - 2, 1)) 55 | draw.line(surface, Theme.WHITE, (1, 1), (1, h - 2)) 56 | draw.line(surface, Theme.DARK_GRAY, (1, h - 1), (w - 1, h - 1)) 57 | draw.line(surface, Theme.DARK_GRAY, (w - 1, 1), (w - 1, h - 1)) 58 | draw.line(surface, Theme.GRAY, (2, h - 2), (w - 2, h - 2)) 59 | draw.line(surface, Theme.GRAY, (w - 2, 2), (w - 2, h - 2)) 60 | 61 | def __repr__(self): 62 | return self.__class__.__name__ 63 | -------------------------------------------------------------------------------- /albow/widgets/ButtonBase.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import event 3 | 4 | from albow.widgets.Control import Control 5 | 6 | 7 | class ButtonBase(Control): 8 | """ 9 | ButtonBase is a mixin class for use by widgets having button-like behaviour. It provides handlers for 10 | mouse events that maintain a "highlighted" state, support for enabling and disabling the button, and s 11 | upport for calling a function when the button is clicked. 12 | 13 | It does not provide any functionality for drawing; that is the responsibility of the subclass using it. 14 | """ 15 | align = 'c' 16 | action = None 17 | """ 18 | A function of no arguments to be called when the button is clicked. May also be defined as 19 | a method in the subclass. 20 | """ 21 | 22 | def mouse_down(self, theEvent: event): 23 | if self.enabled: 24 | self._highlighted = True 25 | 26 | def mouse_drag(self, theEvent: event): 27 | 28 | state = theEvent in self 29 | 30 | if state != self._highlighted: 31 | self._highlighted = state 32 | self.invalidate() 33 | 34 | def mouse_up(self, theEvent: event): 35 | if theEvent in self: 36 | self._highlighted = False 37 | if self.enabled: 38 | self.call_handler('action') # TODO Fix this by using another 'mixin' shared with the Widget class 39 | 40 | def get_highlighted(self): 41 | return self._highlighted 42 | 43 | def get_enabled(self): 44 | enable = self.enable 45 | if enable: 46 | return enable() 47 | else: 48 | return self._enabled 49 | 50 | def set_enabled(self, theNewValue: bool): 51 | self._enabled = theNewValue 52 | -------------------------------------------------------------------------------- /albow/widgets/CheckBox.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.widgets.CheckControl import CheckControl 3 | from albow.widgets.CheckWidget import CheckWidget 4 | 5 | 6 | class CheckBox(CheckControl, CheckWidget): 7 | pass 8 | -------------------------------------------------------------------------------- /albow/widgets/CheckControl.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import event 3 | 4 | from albow.widgets.Control import Control 5 | 6 | 7 | class CheckControl(Control): 8 | 9 | def mouse_down(self, e: event): 10 | self.value = not self.value 11 | 12 | def get_highlighted(self): 13 | return self.value 14 | 15 | -------------------------------------------------------------------------------- /albow/widgets/CheckWidget.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | from pygame import Surface 4 | from pygame import draw 5 | 6 | from albow.core.ui.Widget import Widget 7 | 8 | from albow.themes.ThemeProperty import ThemeProperty 9 | 10 | CHECK_MARK_TWEAK = 2 11 | 12 | 13 | class CheckWidget(Widget): 14 | 15 | default_size = (16, 16) 16 | """ 17 | The default size of the checkbox; Default is 16x16 18 | """ 19 | margin = 4 20 | """ 21 | The margin around the check mark; Default is 4 22 | """ 23 | border_width = 1 24 | """ 25 | This border width of the rectangle around the checkmark; Default is 1 26 | """ 27 | 28 | smooth = ThemeProperty('smooth') 29 | """ 30 | Set to True if you want the checkmark anti-aliased; Default is True 31 | """ 32 | 33 | def __init__(self, **kwds): 34 | super().__init__(Rect((0, 0), self.default_size), **kwds) 35 | 36 | def draw(self, theSurface: Surface): 37 | """ 38 | 39 | Args: 40 | theSurface: The surface to draw on 41 | 42 | 43 | """ 44 | if self.highlighted: 45 | r = self.get_margin_rect() 46 | fg = self.fg_color 47 | d = CHECK_MARK_TWEAK 48 | p1 = (r.left, r.centery - d) 49 | p2 = (r.centerx - d, r.bottom) 50 | p3 = (r.right, r.top - d) 51 | if self.smooth: 52 | draw.aalines(theSurface, fg, False, [p1, p2, p3]) 53 | else: 54 | draw.lines(theSurface, fg, False, [p1, p2, p3]) 55 | 56 | -------------------------------------------------------------------------------- /albow/widgets/Control.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.utils import overridable_property 3 | 4 | 5 | class Control: 6 | """ 7 | Control is a mixin class for use by widgets that display and/or edit a value of some kind. It provides a value 8 | property that can be linked, via a reference object, to a specific attribute or item of another object. 9 | Reading and writing the value property then accesses the specified attribute or item. 10 | 11 | If no such linkage is specified, a value is kept internally to the Control instance, and 12 | the value property accesses this internal value. Thus, a Control-based widget can be used stand-alone if desired. 13 | 14 | """ 15 | highlighted = overridable_property('highlighted') 16 | """ 17 | True if the button should be displayed in a highlighted state. This attribute is maintained by the 18 | default mouse handlers. 19 | """ 20 | enabled = overridable_property('enabled') 21 | """ 22 | A boolean indicating whether the control is enabled. Defaults to True. By default, this property is 23 | read-write and maintains 24 | its own state internal to the object. When an enable function is provided, this property becomes 25 | read-only and gets its value via the supplied function. 26 | """ 27 | value = overridable_property('value') 28 | """ 29 | The current value of the Control. If a ref has been supplied, accesses the value that it specifies. Otherwise, 30 | accesses a value stored internally in a private attribute of the Control. 31 | """ 32 | 33 | enable = None 34 | """ 35 | A function with no arguments that returns a boolean indicating whether the button should be enabled. 36 | May also be defined as a method in the subclass. 37 | """ 38 | ref = None 39 | """ 40 | Reference to an external value. If supplied, it should be a reference object or other object providing 41 | the following methods: 42 | 43 | get() 44 | Should return the current value. 45 | 46 | set(x) 47 | Should set the value to x. 48 | """ 49 | _highlighted: bool = False 50 | _enabled: bool = True 51 | _value = None 52 | 53 | def get_value(self): 54 | ref = self.ref 55 | if ref: 56 | return ref.get() 57 | else: 58 | return self._value 59 | 60 | def set_value(self, x): 61 | ref = self.ref 62 | if ref: 63 | ref.set(x) 64 | else: 65 | self._value = x 66 | -------------------------------------------------------------------------------- /albow/widgets/Image.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Rect 3 | 4 | from albow.core.ResourceUtility import ResourceUtility 5 | 6 | from albow.utils import overridable_property 7 | 8 | from albow.core.ui.Widget import Widget 9 | 10 | from albow.themes.ThemeProperty import ThemeProperty 11 | 12 | 13 | class Image(Widget): 14 | """ 15 | An Image is a widget that displays an image. 16 | """ 17 | highlight_color = ThemeProperty('highlight_color') 18 | """ 19 | The image highlight color 20 | """ 21 | image = overridable_property('image') 22 | """ 23 | The image to display. The behaviour of this property can be customised by overriding the `get_image()` method. 24 | """ 25 | highlighted = False 26 | """ 27 | Indicates whether or not to highlight the image; Default is _False_ 28 | """ 29 | 30 | def __init__(self, theImage = None, theRect: Rect = None, **kwds): 31 | """ 32 | TODO Do a unit test on this class 33 | 34 | Initializes the widget to display the given image. The initial size is determined by the image. 35 | 36 | Args: 37 | theImage: Either a string that is the image name and can be found via the resource lookup methods 38 | or an actual image. (TBD) - I don't like this and will change the API to only accept image names 39 | 40 | theRect: The pygame rectangle to draw in 41 | 42 | **kwds: 43 | """ 44 | super().__init__(theRect, **kwds) 45 | 46 | if theImage: 47 | if isinstance(theImage, str): 48 | theImage = ResourceUtility.get_image(theImage) 49 | w, h = theImage.get_size() 50 | d = 2 * self.margin 51 | self.size = w + d, h + d 52 | self._image = theImage 53 | else: 54 | self._image = None 55 | 56 | def get_image(self): 57 | """ 58 | Called to get the value of the image property. By overriding this method, you can make the widget display 59 | an image from an outside source. 60 | 61 | Returns: The pygame image 62 | 63 | """ 64 | return self._image 65 | 66 | def set_image(self, x): 67 | self._image = x 68 | 69 | def draw(self, surf): 70 | if self.highlighted: 71 | surf.fill(self.highlight_color) 72 | self.draw_image(surf, self.image) 73 | 74 | def draw_image(self, surf, image): 75 | frame = surf.get_rect() 76 | r = image.get_rect() 77 | r.center = frame.center 78 | surf.blit(image, r) 79 | -------------------------------------------------------------------------------- /albow/widgets/ImageButton.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Surface 3 | 4 | import logging 5 | 6 | from albow.core.ResourceUtility import ResourceUtility 7 | 8 | from albow.utils import overridable_property 9 | 10 | from albow.widgets.ButtonBase import ButtonBase 11 | 12 | from albow.widgets.Image import Image 13 | 14 | 15 | class ImageButton(ButtonBase, Image): 16 | """ 17 | 18 | An ImageButton is a button whose appearance is defined by an image. 19 | """ 20 | disabledBgImage = overridable_property('disabledBgImage') 21 | """ 22 | This disabled background image 23 | """ 24 | enabledBgImage = overridable_property('enabledBgImage') 25 | """ 26 | The enabled background image 27 | """ 28 | highlightedBgImage = overridable_property('highlightedBgImage') 29 | """ 30 | The highlighted background image 31 | """ 32 | def __init__(self, disabledBgImage: str = None, enabledBgImage: str = None, highlightedBgImage: str = None, **kwds): 33 | """ 34 | You must as a minimum supply a single image via `theImage` parameter. Optionally, you can supply 35 | enabled, disabled, and highlighted images 36 | 37 | Args: 38 | disabledBgImage: The image to display when the button is disabled 39 | 40 | enabledBGImage: The image to display when the button is enabled 41 | 42 | highlightedBgImage: The image to display when the button is highlighted 43 | 44 | **kwds: 45 | """ 46 | Image.__init__(self, **kwds) 47 | 48 | self.logger = logging.getLogger(__name__) 49 | self._disabledBgImage = None 50 | self._enabledBgImage = None 51 | self._highlightedBgImage = None 52 | 53 | if disabledBgImage != None: 54 | self._disabledBgImage = ResourceUtility.get_image(disabledBgImage) 55 | 56 | if enabledBgImage != None: 57 | self._enabledBgImage = ResourceUtility.get_image(enabledBgImage) 58 | 59 | if highlightedBgImage != None: 60 | self._highlightedBgImage = ResourceUtility.get_image(highlightedBgImage) 61 | 62 | def get_disabledBgImage(self): 63 | return self._disabledBgImage 64 | 65 | def set_disabledBgImage(self, theNewImage: Surface): 66 | self._disabledBgImage = theNewImage 67 | 68 | def get_enabledBgImage(self): 69 | return self._enabledBgImage 70 | 71 | def set_enabledBgImage(self, theNewImage: Surface): 72 | self._enabledBgImage = theNewImage 73 | 74 | def get_highlightedBgImage(self) -> Surface: 75 | return self._highlightedBgImage 76 | 77 | def set_highlightedBgImage(self, theNewImage: Surface): 78 | self._highlightedBgImage = theNewImage 79 | 80 | def get_highlighted(self): 81 | return self._highlighted 82 | 83 | def set_highlighted(self, theNewValue: bool): 84 | self._highlighted = theNewValue 85 | 86 | def draw(self, surface: Surface): 87 | 88 | dbi = self.disabledBgImage 89 | ebi = self.enabledBgImage 90 | hbi = self.highlightedBgImage 91 | 92 | if not self.enabled: 93 | if dbi: 94 | self.draw_image(surface, dbi) 95 | elif self.highlighted: 96 | if hbi: 97 | self.draw_image(surface, hbi) 98 | else: 99 | surface.fill(self.highlight_color) 100 | else: 101 | if ebi: 102 | self.draw_image(surface, ebi) 103 | fgi = self.image 104 | if fgi: 105 | self.draw_image(surface, fgi) 106 | -------------------------------------------------------------------------------- /albow/widgets/ListBox.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Tuple 3 | 4 | import logging 5 | 6 | from pygame import Surface 7 | from pygame import Rect 8 | from pygame.event import Event 9 | 10 | from albow.core.ui.Widget import Widget 11 | 12 | from albow.containers.PaletteView import PaletteView 13 | 14 | LISTBOX_DEFAULT_ROWS: int = 10 15 | LISTBOX_COLUMNS: int = 1 16 | LISTBOX_SCROLLS: bool = True 17 | 18 | 19 | class ListBox(PaletteView): 20 | 21 | """ 22 | 23 | """ 24 | client: Widget = None 25 | selection: int = None 26 | items = [] 27 | selectAction = None 28 | 29 | def __init__(self, theClient: Widget, theItems: list, 30 | nrows: int = LISTBOX_DEFAULT_ROWS, ncols: int = LISTBOX_COLUMNS, selectAction=None, **kwargs): 31 | 32 | assert isinstance(theItems, list), "Wrong type for input list" 33 | self.client = theClient 34 | self.items = theItems 35 | self.selectAction = selectAction 36 | 37 | font = self.predict_font(kwargs) 38 | h = font.get_linesize() 39 | d = 2 * self.predict(kwargs, 'margin') 40 | 41 | longestTextLine: str = self._getLongestTextLine() 42 | lineDimension: Tuple[int, int] = font.size(longestTextLine) 43 | 44 | listBoxWidth: int = lineDimension[0] 45 | cellSize = (listBoxWidth - d, h) 46 | 47 | self.border_width = 1 # 48 | self.margin = 5 # Before constructor to allow proper sizing 2.7.4 49 | self.highlight_style = 'reverse' # 50 | 51 | super().__init__(cell_size=cellSize, nrows=nrows, ncols=ncols, scrolling=LISTBOX_SCROLLS, **kwargs) 52 | 53 | self.client = theClient 54 | self.items = theItems 55 | self.selectAction = selectAction 56 | self.logger = logging.getLogger(__name__) 57 | 58 | # 59 | # The 4 methods below implement the PaletteView abstract methods 60 | # 61 | 62 | def num_items(self) -> int: 63 | return len(self.items) 64 | 65 | def draw_item(self, theSurface: Surface, theItemNumber: int, theRect: Rect): 66 | 67 | # self.logger.info("draw_item %s ", theRect.size) 68 | 69 | color = self.fg_color 70 | buf = self.font.render(self.items[theItemNumber], True, color) 71 | 72 | theSurface.blit(buf, theRect) 73 | 74 | def click_item(self, theItemNumber: int, theEvent: Event): 75 | 76 | self.logger.debug("click_item: %s", theItemNumber) 77 | 78 | self.selection = theItemNumber 79 | if self.selectAction is not None: 80 | self.selectAction(self.items[self.selection]) 81 | 82 | def item_is_selected(self, theItemNumber: int) -> bool: 83 | 84 | ans: bool = theItemNumber == self.selection 85 | return ans 86 | 87 | def _getLongestTextLine(self) -> str: 88 | 89 | retStr: str = "" 90 | for currLine in self.items: 91 | if len(currLine) > len(retStr): 92 | retStr = currLine 93 | return retStr 94 | -------------------------------------------------------------------------------- /albow/widgets/RadioButton.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.widgets.CheckWidget import CheckWidget 3 | from albow.widgets.RadioControl import RadioControl 4 | 5 | 6 | class RadioButton(RadioControl, CheckWidget): 7 | """ 8 | RadioButton controls are intended to be used in a group to provide a multiple-choice selection. To achieve 9 | this, all the radio buttons in the group should be linked via their ref attributes to the same value, and each 10 | one given a unique setting. The one whose setting matches the current value displays its check mark, and 11 | clicking on a radio button sets the value to that button's setting. 12 | 13 | Note that a RadioButton does not have a title; you will need to place a Label beside it if you want one. 14 | 15 | The visual appearance of a RadioButton is currently the same as a CheckBox. This may change in a later version. 16 | """ 17 | pass 18 | -------------------------------------------------------------------------------- /albow/widgets/RadioControl.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import event 3 | from albow.widgets.Control import Control 4 | 5 | 6 | class RadioControl(Control): 7 | 8 | setting = None 9 | """ 10 | The setting of the value that this radio button corresponds to. 11 | """ 12 | 13 | def get_highlighted(self): 14 | return self.value == self.setting 15 | 16 | def mouse_down(self, e: event): 17 | self.value = self.setting 18 | 19 | -------------------------------------------------------------------------------- /albow/widgets/ValueDisplay.py: -------------------------------------------------------------------------------- 1 | 2 | from pygame import Surface 3 | 4 | from albow.core.ui.Widget import Widget 5 | 6 | from albow.widgets.Control import Control 7 | 8 | from albow.utils import blit_in_rect 9 | 10 | 11 | class ValueDisplay(Control, Widget): 12 | 13 | """ 14 | A ValueDisplay is a Control that provides a read-only textual display of a value. 15 | """ 16 | format = "%s" 17 | """ 18 | Format string to be used when displaying the value. Also see the format_value() method below. 19 | 20 | """ 21 | align = 'l' 22 | """ 23 | How to align the value. Default is 'l' 24 | """ 25 | 26 | def __init__(self, width=100, **kwds): 27 | 28 | Widget.__init__(self, **kwds) 29 | self.set_size_for_text(width) 30 | 31 | def draw(self, surface: Surface): 32 | 33 | value = self.value 34 | text = self.format_value(value) 35 | buf = self.font.render(text, True, self.fg_color) 36 | frame = surface.get_rect() 37 | blit_in_rect(surface, buf, frame, self.align, self.margin) 38 | 39 | def format_value(self, value): 40 | if value is not None: 41 | return self.format % value 42 | else: 43 | return "" 44 | -------------------------------------------------------------------------------- /albow/widgets/WidgetUtilities.py: -------------------------------------------------------------------------------- 1 | 2 | import textwrap 3 | 4 | from albow.widgets.Label import Label 5 | 6 | 7 | def wrapped_label(text: str, wrap_width: int, **kwds) -> Label: 8 | """ 9 | Constructs a `albow.widgets.Label` widget from the given text after using the ``textwrap`` module to wrap it to 10 | the specified width in characters. 11 | 12 | Additional keyword parameters are passed to the Label constructor. 13 | 14 | Args: 15 | text: The text to wrap in a label 16 | 17 | wrap_width: The wrap width 18 | 19 | **kwds: Pass these to the Label constructor 20 | 21 | Returns: A Label widget 22 | 23 | """ 24 | paras = text.split("\n\n") 25 | text = "\n".join([textwrap.fill(para, wrap_width) for para in paras]) 26 | 27 | return Label(text, **kwds) 28 | 29 | -------------------------------------------------------------------------------- /albow/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains the basic Albow widgets 3 | """ -------------------------------------------------------------------------------- /docs/Albow.put: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/docs/Albow.put -------------------------------------------------------------------------------- /docs/ImportProject.put: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/docs/ImportProject.put -------------------------------------------------------------------------------- /docs/Tables.put: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/docs/Tables.put -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pygame~=2.0.1 2 | wheel~=0.35.1 3 | setuptools~=54.1.1 4 | twine~=3.3.0 5 | -------------------------------------------------------------------------------- /scripts/cleanup.sh: -------------------------------------------------------------------------------- 1 | 2 | function changeToProjectRoot { 3 | 4 | export areHere=`basename ${PWD}` 5 | if [[ ${areHere} = "scripts" ]]; then 6 | cd .. 7 | fi 8 | } 9 | 10 | changeToProjectRoot 11 | 12 | rm -rf dist build python3_albow.egg-info 13 | -------------------------------------------------------------------------------- /scripts/packageme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Assumes python 3 is on PATH 4 | # 5 | 6 | function changeToProjectRoot { 7 | 8 | export areHere=`basename ${PWD}` 9 | if [[ ${areHere} = "scripts" ]]; then 10 | cd .. 11 | fi 12 | } 13 | 14 | changeToProjectRoot 15 | 16 | clear 17 | 18 | source venv-pyenv-3.8.5/bin/activate 19 | ./scripts/cleanup.sh 20 | python3 setup.py sdist bdist_wheel 21 | 22 | # Check package 23 | twine check dist/* 24 | -------------------------------------------------------------------------------- /scripts/pushtoprod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function changeToProjectRoot { 4 | 5 | export areHere=`basename ${PWD}` 6 | if [[ ${areHere} = "scripts" ]]; then 7 | cd .. 8 | fi 9 | } 10 | 11 | changeToProjectRoot 12 | 13 | twine upload dist/* 14 | 15 | -------------------------------------------------------------------------------- /scripts/pushtotest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function changeToProjectRoot { 4 | 5 | export areHere=`basename ${PWD}` 6 | if [[ ${areHere} = "scripts" ]]; then 7 | cd .. 8 | fi 9 | } 10 | 11 | changeToProjectRoot 12 | 13 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 14 | -------------------------------------------------------------------------------- /scripts/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function changeToProjectRoot { 4 | 5 | export areHere=`basename ${PWD}` 6 | if [[ ${areHere} = "scripts" ]]; then 7 | cd .. 8 | fi 9 | } 10 | 11 | changeToProjectRoot 12 | 13 | python3 -m test.RunTests 14 | 15 | status=$? 16 | 17 | echo "Exit with status: ${status}" 18 | exit ${status} 19 | 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | 5 | # The directory containing this file 6 | HERE = pathlib.Path(__file__).parent 7 | 8 | # The text of the README file 9 | README = (HERE / "README.md").read_text() 10 | 11 | DATA_FILES = [('albow/themes/resources', ['albow/themes/resources/default-theme.ini']), 12 | ('albow/themes/resources', ['albow/themes/resources/Vera.ttf']), 13 | ('albow/themes/resources', ['albow/themes/resources/VeraBd.ttf']), 14 | ] 15 | setup( 16 | name="python3-albow", 17 | version="2.90.0", 18 | author='Humberto A. Sanchez II', 19 | author_email='Humberto.A.Sanchez.II@gmail.com', 20 | description="A Little Bit of Widgetry for PyGame", 21 | long_description=README, 22 | long_description_content_type="text/markdown", 23 | url="https://github.com/hasii2011/albow-python-3", 24 | packages=find_packages(), 25 | include_package_data=True, 26 | install_requires=['pygame'] 27 | ) 28 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | -------------------------------------------------------------------------------- /test/DummyControl.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from albow.widgets.Control import Control 5 | 6 | from albow.themes.ThemeProperty import ThemeProperty 7 | 8 | 9 | class DummyControl(Control): 10 | 11 | dummyThemeAttribute = ThemeProperty('dummyThemeAttribute') 12 | 13 | """" 14 | A dummy control for unit testing 15 | 16 | """ 17 | def __init__(self, **attrs): 18 | 19 | self.logger = logging.getLevelName(__name__) 20 | self.set(**attrs) 21 | self._dummyThemeAttribute = None 22 | 23 | def getDummyThemeAttribute(self): 24 | return self._dummyThemeAttribute 25 | 26 | def setDummyThemeAttribute(self, theNewValue): 27 | self._dummyThemeAttribute = theNewValue 28 | 29 | def set(self, **kwds): 30 | 31 | for name, value in kwds.items(): 32 | if not hasattr(self, name): 33 | raise TypeError("Unexpected keyword argument '%s'" % name) 34 | setattr(self, name, value) 35 | 36 | def __repr__(self): 37 | formattedMe: str = \ 38 | f"DummyControl(value: '{self._value}' enabled: '{self._enabled}' highlighted: '{self._highlighted})'" 39 | return formattedMe 40 | -------------------------------------------------------------------------------- /test/DummyVehicle.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from albow.utils import overridable_property 5 | 6 | 7 | class DummyVehicle: 8 | 9 | velocity = overridable_property(name='velocity', doc="How fast we are going") 10 | weight = overridable_property(name='weight', doc="How fat we are") 11 | width = overridable_property(name='width', doc="How wide our butt is") 12 | height = overridable_property(name='height', doc="How tall we are not") 13 | 14 | def __init__(self): 15 | 16 | self.logger = logging.getLogger(__name__) 17 | 18 | self._velocity = 0 19 | self._weight = 0 20 | self._width = 0 21 | self._height = 0 22 | 23 | def get_velocity(self): 24 | return self._velocity 25 | 26 | def set_velocity(self, theNewValue: int): 27 | self._velocity = theNewValue 28 | 29 | def get_weight(self): 30 | return self._weight 31 | 32 | def set_weight(self, theNewValue: int): 33 | self._weight = theNewValue 34 | 35 | def get_width(self): 36 | return self._width 37 | 38 | def set_width(self, theNewValue: int): 39 | self._width = theNewValue 40 | 41 | def get_height(self): 42 | return self._height 43 | 44 | def set_height(self, theNewValue: int): 45 | self._height = theNewValue 46 | 47 | def __eq__(self, theOtherOne): 48 | 49 | if isinstance(theOtherOne, DummyVehicle): 50 | if self._velocity == theOtherOne.velocity and \ 51 | self._weight == theOtherOne.weight and \ 52 | self._width == theOtherOne.width and \ 53 | self._height == theOtherOne.height: 54 | return True 55 | else: 56 | return False 57 | 58 | def __repr__(self): 59 | return f'{self.velocity=} {self.weight=} {self.width=} {self.height=}' 60 | 61 | -------------------------------------------------------------------------------- /test/DummyWidget.py: -------------------------------------------------------------------------------- 1 | 2 | from albow.core.ui.Widget import Widget 3 | 4 | 5 | class DummyWidget(Widget): 6 | 7 | def __init__(self, **kwds): 8 | 9 | super().__init__(rect=None, **kwds) 10 | -------------------------------------------------------------------------------- /test/RunTests.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from unittest import TestResult 4 | from unittest import TestSuite 5 | from unittest import TestLoader 6 | from unittest import TextTestRunner 7 | 8 | from test.TestFindResources import TestFindResources 9 | from test.TestPredictor import TestPredictor 10 | from test.TestReferences import TestReferences 11 | from test.TestResourceUtility import TestResourceUtility 12 | from test.TestScheduledCall import TestScheduledCall 13 | from test.TestTheme import TestTheme 14 | from test.TestThemeProperty import TestThemeProperty 15 | 16 | from test.TestScheduler import TestScheduler 17 | 18 | 19 | def main(): 20 | # Initialize the test suite 21 | testLoader: TestLoader = TestLoader() 22 | suite: TestSuite = TestSuite() 23 | 24 | suite.addTest(testLoader.loadTestsFromTestCase(TestFindResources)) 25 | suite.addTest(testLoader.loadTestsFromTestCase(TestPredictor)) 26 | suite.addTest(testLoader.loadTestsFromTestCase(TestReferences)) 27 | suite.addTest(testLoader.loadTestsFromTestCase(TestResourceUtility)) 28 | suite.addTest(testLoader.loadTestsFromTestCase(TestTheme)) 29 | suite.addTest(testLoader.loadTestsFromTestCase(TestThemeProperty)) 30 | 31 | suite.addTest(testLoader.loadTestsFromTestCase(TestScheduledCall)) 32 | suite.addTest(testLoader.loadTestsFromTestCase(TestScheduler)) 33 | 34 | # initialize a runner, pass it our suite and run it 35 | runner = TextTestRunner() 36 | result: TestResult = runner.run(suite) 37 | 38 | print(result) 39 | 40 | if len(result.failures) != 0: 41 | return 1 42 | else: 43 | return 0 44 | 45 | 46 | if __name__ == '__main__': 47 | 48 | cliStatus: int = main() 49 | exit(cliStatus) 50 | -------------------------------------------------------------------------------- /test/TestBase.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import json 4 | import logging 5 | import logging.config 6 | 7 | from unittest import TestCase 8 | 9 | JSON_LOGGING_CONFIG_FILENAME = "testLoggingConfig.json" 10 | 11 | 12 | class TestBase(TestCase): 13 | """ 14 | A base unit test class to initialize some logging stuff we need 15 | """ 16 | 17 | RESOURCES_PACKAGE_NAME: str = 'test.testresources' 18 | 19 | @classmethod 20 | def setUpLogging(cls): 21 | """""" 22 | loggingConfigFilename: str = cls.findLoggingConfig() 23 | 24 | with open(loggingConfigFilename, 'r') as loggingConfigurationFile: 25 | configurationDictionary = json.load(loggingConfigurationFile) 26 | 27 | logging.config.dictConfig(configurationDictionary) 28 | logging.logProcesses = False 29 | logging.logThreads = False 30 | 31 | @classmethod 32 | def findLoggingConfig(cls) -> str: 33 | """""" 34 | upDir = f'test/{JSON_LOGGING_CONFIG_FILENAME}' 35 | if os.path.isfile(upDir): 36 | return upDir 37 | 38 | if os.path.isfile(JSON_LOGGING_CONFIG_FILENAME): 39 | return JSON_LOGGING_CONFIG_FILENAME 40 | else: 41 | os.chdir("../") 42 | return cls.findLoggingConfig() 43 | -------------------------------------------------------------------------------- /test/TestFindResources.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | import unittest 5 | 6 | from test.TestBase import TestBase 7 | 8 | from pkg_resources import resource_filename 9 | from pkg_resources import resource_exists 10 | 11 | BUILT_IN_BOLD_FONT_NAME = "VeraBd.ttf" 12 | BUILT_IN_FONT_NAME = "Vera.ttf" 13 | DEFAULT_THEME_FILENAME = "default-theme.ini" 14 | THEMES_RESOURCES_PACKAGE = "albow.themes.resources" 15 | 16 | 17 | class TestFindResources(TestBase): 18 | 19 | @classmethod 20 | def setUpClass(cls): 21 | """""" 22 | TestBase.setUpLogging() 23 | 24 | def setUp(self): 25 | """""" 26 | self.logger = logging.getLogger(__name__) 27 | 28 | def testFindDefaultTheme(self): 29 | 30 | self.assertTrue(resource_exists(THEMES_RESOURCES_PACKAGE, DEFAULT_THEME_FILENAME), "Missing theme file") 31 | fileName = resource_filename(THEMES_RESOURCES_PACKAGE, DEFAULT_THEME_FILENAME) 32 | 33 | self.assertIsNotNone(fileName) 34 | self.logger.info(f"fileName: `{fileName}`") 35 | 36 | def testFindBuiltInBaseFont(self): 37 | 38 | self.assertTrue(resource_exists(THEMES_RESOURCES_PACKAGE, "%s" % BUILT_IN_FONT_NAME), "Missing built-in font") 39 | fontFileName = resource_filename(THEMES_RESOURCES_PACKAGE, BUILT_IN_FONT_NAME) 40 | 41 | self.assertIsNotNone(fontFileName) 42 | self.logger.info(f"fontFileName: `{fontFileName}`") 43 | 44 | def testFindBuiltInBoldBaseFont(self): 45 | 46 | self.assertTrue(resource_exists(THEMES_RESOURCES_PACKAGE, BUILT_IN_BOLD_FONT_NAME), "Missing built-in bold font") 47 | 48 | boldFontFileName = resource_filename(THEMES_RESOURCES_PACKAGE, "%s" % BUILT_IN_BOLD_FONT_NAME) 49 | 50 | self.assertIsNotNone(boldFontFileName) 51 | self.logger.info(f"boldFontFileName: `{boldFontFileName}`") 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /test/TestPredictor.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | 4 | from pygame.font import Font 5 | 6 | import pygame 7 | 8 | 9 | from albow.themes.Theme import Theme 10 | from albow.themes.ThemeLoader import ThemeLoader 11 | 12 | from albow.core.ui.Predictor import Predictor 13 | 14 | from test.TestBase import TestBase 15 | 16 | from test.DummyWidget import DummyWidget 17 | 18 | 19 | class TestPredictor(TestBase): 20 | 21 | @classmethod 22 | def setUpClass(cls): 23 | """""" 24 | TestBase.setUpLogging() 25 | themeLoader: ThemeLoader = ThemeLoader() 26 | themeLoader.load() 27 | themeRoot: Theme = themeLoader.themeRoot 28 | Theme.setThemeRoot(themeRoot) 29 | 30 | def setUp(self): 31 | """""" 32 | self.logger = logging.getLogger(__name__) 33 | 34 | def testBasic(self): 35 | 36 | dummyWidget: DummyWidget = DummyWidget() 37 | predictor: Predictor = Predictor(dummyWidget) 38 | 39 | kwds = { 40 | 'margin': 5 41 | } 42 | val = predictor.predict(kwds, 'margin') 43 | self.assertIsNotNone(val, "Something is broken") 44 | self.assertEqual(val, 5, "Predicted incorrectly") 45 | 46 | val = predictor.predict(kwds, 'bg_color') 47 | self.assertIsNotNone(val, "Should get it from widget") 48 | self.assertEqual(val, (208, 210, 211), "Got wrong value") 49 | 50 | def testBasicPredictAttr(self): 51 | 52 | dummyWidget: DummyWidget = DummyWidget() 53 | predictor: Predictor = Predictor(dummyWidget) 54 | 55 | kwds = {} 56 | val = predictor.predict_attr(kwds, 'is_modal') 57 | 58 | self.assertIsNotNone(val, "Should get it from widget") 59 | self.assertEqual(val, False, "Wrong value") 60 | 61 | def testBasicPredictFont(self): 62 | 63 | dummyWidget: DummyWidget = DummyWidget() 64 | predictor: Predictor = Predictor(dummyWidget) 65 | 66 | pygame.init() 67 | kwds = {} 68 | val: Font = predictor.predict_font(kwds) 69 | 70 | self.assertIsNotNone(val, "Should get it from widget") 71 | -------------------------------------------------------------------------------- /test/TestReferences.py: -------------------------------------------------------------------------------- 1 | 2 | from logging import Logger 3 | from logging import getLogger 4 | 5 | from unittest import expectedFailure 6 | from unittest import main as unitTestMain 7 | 8 | from test.DummyControl import DummyControl 9 | from test.DummyVehicle import DummyVehicle 10 | 11 | from albow.ItemRefInsertionException import ItemRefInsertionException 12 | 13 | from albow.References import AttrRef 14 | from albow.References import ItemRef 15 | 16 | 17 | from test.TestBase import TestBase 18 | 19 | 20 | TEST_ITEM_INDEX = 3 21 | 22 | 23 | class TestReferences(TestBase): 24 | """ 25 | """ 26 | 27 | @classmethod 28 | def setUpClass(cls): 29 | """""" 30 | TestBase.setUpLogging() 31 | 32 | def setUp(self): 33 | """""" 34 | self.logger: Logger = getLogger(__name__) 35 | 36 | def testBasicAttrRef(self): 37 | 38 | testVehicle: DummyVehicle = DummyVehicle() 39 | 40 | velocityRef = AttrRef(base=testVehicle, name="velocity") 41 | self.logger.info("Created: %s", velocityRef) 42 | 43 | velocityControl: DummyControl = DummyControl(ref=velocityRef) 44 | self.logger.info("Created: %s", velocityControl) 45 | # 46 | # Change the data model 47 | # 48 | testVehicle.velocity = 100 49 | 50 | self.assertTrue(velocityControl.get_value() == testVehicle.velocity, "Reference did not update control") 51 | 52 | testVehicle.velocity = 200 53 | self.assertTrue(velocityControl.get_value() == testVehicle.velocity, "Reference did not update control") 54 | # 55 | # Change the control 56 | # 57 | velocityControl.set_value(500) 58 | self.assertTrue(velocityControl.get_value() == testVehicle.velocity, "Control did not update reference") 59 | 60 | @expectedFailure 61 | def testBadItemRefInsertion(self): 62 | 63 | vehicleList = self.getVehicleList() 64 | 65 | itemRef = ItemRef(base=vehicleList, index=TEST_ITEM_INDEX) 66 | self.logger.info("Created %s", itemRef) 67 | 68 | velocityControl: DummyControl = DummyControl(ref=itemRef) 69 | self.logger.info("Created velocity control %s", velocityControl) 70 | # 71 | # Change the control 72 | # 73 | try: 74 | velocityControl.set_value(500) 75 | self.assertTrue(velocityControl.get_value() == vehicleList[TEST_ITEM_INDEX].velocity, "Control did not update reference") 76 | except ItemRefInsertionException as e: 77 | self.logger.debug(f"{e.message}") 78 | self.fail('Expected failure') 79 | 80 | def testBasicItemRefRetrieval(self): 81 | 82 | vehicleList = self.getVehicleList() 83 | 84 | itemRef = ItemRef(base=vehicleList, index=TEST_ITEM_INDEX) 85 | self.logger.info(f"Created {itemRef=}") 86 | 87 | velocityControl: DummyControl = DummyControl(ref=itemRef) 88 | self.logger.info("Created velocity control %s", velocityControl) 89 | 90 | anItem = itemRef.get() 91 | self.assertEqual(first=vehicleList[TEST_ITEM_INDEX], second=anItem, msg="Did not retrieve what I put in.") 92 | 93 | def testItemRefIndexing(self): 94 | 95 | vehicleList = self.getVehicleList() 96 | 97 | itemRef = ItemRef(base=vehicleList, index=TEST_ITEM_INDEX) 98 | self.logger.info("Created: %s", itemRef) 99 | 100 | testItem = itemRef[TEST_ITEM_INDEX] 101 | self.logger.info("Retrieved: %s", testItem) 102 | self.assertEqual(first=vehicleList[TEST_ITEM_INDEX], second=testItem, msg="Did not retrieve the correct item.") 103 | 104 | @staticmethod 105 | def getVehicleList(): 106 | 107 | vehicleList = [] 108 | for i in range(0, 5): 109 | testVehicle: DummyVehicle = DummyVehicle() 110 | testVehicle.weight = i * 100 111 | testVehicle.velocity = (i * 2) * 10 112 | vehicleList.append(testVehicle) 113 | 114 | return vehicleList 115 | 116 | 117 | if __name__ == '__main__': 118 | unitTestMain() 119 | -------------------------------------------------------------------------------- /test/TestResourceUtility.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from logging import Logger 5 | from logging import getLogger 6 | 7 | from unittest import main as unitTestMain 8 | from unittest import expectedFailure 9 | 10 | from pkg_resources import resource_filename 11 | 12 | from test.TestBase import TestBase 13 | 14 | from albow.core.ResourceUtility import ResourceUtility 15 | 16 | UNIT_TEST_DIR_NAME: str = 'test' 17 | RESOURCE_DIR_NAME: str = 'testresources' 18 | TEST_SOUND_RELATIVE_PATH: str = f'TestSound.mp3' 19 | 20 | 21 | class TestResourceUtility(TestBase): 22 | 23 | ourLogger: Logger = None 24 | 25 | @classmethod 26 | def setUpClass(cls): 27 | """""" 28 | TestBase.setUpLogging() 29 | 30 | def setUp(self): 31 | """""" 32 | TestResourceUtility.ourLogger = getLogger(__name__) 33 | self.logger = TestResourceUtility.ourLogger 34 | 35 | @expectedFailure 36 | def testFindResourceDirFailure(self): 37 | ResourceUtility.find_resource_dir() 38 | 39 | def testFindResourceDirSuccess(self): 40 | 41 | currentDirectory = os.getcwd() 42 | self.logger.info(f"cwd: '{currentDirectory}'") 43 | 44 | newDir = "resources" 45 | os.mkdir(newDir) 46 | resourceDir = ResourceUtility.find_resource_dir() 47 | 48 | self.assertIsNotNone(resourceDir, "Failed to return something") 49 | 50 | self.assertEqual(resourceDir.lower(), f"{currentDirectory}/resources".lower(), "Found in wrong place") 51 | os.rmdir("resources") 52 | 53 | def testLoadSound(self): 54 | 55 | import pygame 56 | pygame.init() 57 | 58 | fqFileName: str = self.getFullResourcePath(TEST_SOUND_RELATIVE_PATH) 59 | 60 | dummySound = ResourceUtility.load_sound(fqFileName) 61 | self.logger.info(f"{dummySound}") 62 | 63 | def testLoadSoundFail(self): 64 | 65 | fqFileName: str = self.getFullResourcePath(TEST_SOUND_RELATIVE_PATH) 66 | 67 | ResourceUtility.sound_cache = None 68 | dummySound = ResourceUtility.load_sound(fqFileName) 69 | 70 | self.assertEqual(first=ResourceUtility.dummy_sound, second=dummySound, msg="Did not get the dummy sound") 71 | 72 | @expectedFailure 73 | def testGetImageFail(self): 74 | 75 | ResourceUtility.get_image("") 76 | 77 | def getFullResourcePath(self, filename: str) -> str: 78 | 79 | fqFileName: str = resource_filename(TestBase.RESOURCES_PACKAGE_NAME, filename) 80 | 81 | return fqFileName 82 | 83 | 84 | if __name__ == '__main__': 85 | unitTestMain() 86 | -------------------------------------------------------------------------------- /test/TestTheme.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | import logging 5 | 6 | from logging import Logger 7 | 8 | import pygame 9 | 10 | from test.TestBase import TestBase 11 | 12 | from albow.themes.Theme import Theme 13 | 14 | THEMES_RESOURCES_PACKAGE = "albow.themes.resources" 15 | BUILT_IN_BOLD_FONT_NAME = "VeraBd.ttf" 16 | 17 | 18 | class TestTheme(TestBase): 19 | 20 | ourLogger: Logger = None 21 | 22 | @classmethod 23 | def setUpClass(cls): 24 | """""" 25 | TestBase.setUpLogging() 26 | TestTheme.ourLogger = logging.getLogger(__name__) 27 | 28 | def setUp(self): 29 | """""" 30 | self.logger = TestTheme.ourLogger 31 | 32 | def tearDown(self): 33 | 34 | Theme.fontCache = {} 35 | 36 | def testFontLoad(self): 37 | 38 | testTheme: Theme = Theme(name="bogus") 39 | fontSpec: tuple = (18, BUILT_IN_BOLD_FONT_NAME) 40 | fontPath: str = testTheme._findFontFile(fontSpec) 41 | 42 | exists = os.path.isfile(fontPath) 43 | self.assertTrue(exists, "Where is my font!") 44 | 45 | def testFontCacheLoad(self): 46 | 47 | self.assertTrue(len(Theme.fontCache) == 0, "Oops not in an initial state") 48 | 49 | pygame.init() 50 | 51 | testTheme: Theme = Theme(name="bogus") 52 | 53 | fontSpec: tuple = (18, BUILT_IN_BOLD_FONT_NAME) 54 | fontPath: str = testTheme._findFontFile(fontSpec) 55 | testTheme._loadFont(fontPath=fontPath, fontSize=fontSpec[0]) 56 | 57 | self.assertTrue(len(Theme.fontCache) == 1, "Oops cache is not working") 58 | 59 | def testFontCacheHit(self): 60 | pygame.init() 61 | 62 | testTheme: Theme = Theme(name="bogus") 63 | 64 | fontSpec: tuple = (18, BUILT_IN_BOLD_FONT_NAME) 65 | fontPath: str = testTheme._findFontFile(fontSpec) 66 | font1 = testTheme._loadFont(fontPath=fontPath, fontSize=fontSpec[0]) 67 | font2 = testTheme._loadFont(fontPath=fontPath, fontSize=fontSpec[0]) 68 | 69 | self.assertEqual(first=font1, second=font2, msg="Should be the one from the cache") 70 | -------------------------------------------------------------------------------- /test/TestThemeProperty.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from logging import Logger 4 | 5 | import unittest 6 | 7 | from test.TestBase import TestBase 8 | 9 | from test.DummyControl import DummyControl 10 | 11 | 12 | class TestThemeProperty(TestBase): 13 | 14 | classLogger: Logger = None 15 | 16 | @classmethod 17 | def setUpClass(cls): 18 | """""" 19 | TestBase.setUpLogging() 20 | 21 | def setUp(self): 22 | """""" 23 | TestThemeProperty.classLogger = logging.getLogger(__name__) 24 | 25 | def tearDown(self): 26 | """ 27 | cleanup 28 | """ 29 | 30 | def testBaseThemeProperty(self): 31 | 32 | dummyControl: DummyControl = DummyControl() 33 | dummyControl.setDummyThemeAttribute(42.0) 34 | 35 | themeVal = dummyControl.getDummyThemeAttribute() 36 | self.assertIsNotNone(themeVal, "Broken theme property setter") 37 | 38 | def testBareException(self): 39 | 40 | logger = TestThemeProperty.classLogger 41 | dummyControl: DummyControl = DummyControl() 42 | dummyControl.setDummyThemeAttribute(42.0) 43 | 44 | themeVal = dummyControl.getDummyThemeAttribute() 45 | logger.info(f"themeVal: {themeVal}") 46 | 47 | themeVal = dummyControl._dummyThemeAttribute = 52 48 | 49 | self.assertEqual(themeVal, 52, "Change to underlying value is broken") 50 | 51 | 52 | if __name__ == '__main__': 53 | unittest.main(exit=False) 54 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/test/__init__.py -------------------------------------------------------------------------------- /test/testLoggingConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "disable_existing_loggers": "False", 4 | "formatters": { 5 | "simple": { 6 | "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 7 | }, 8 | "demoSimple": { 9 | "format": "%(levelname)s: %(module)s: %(message)s" 10 | } 11 | }, 12 | "handlers": { 13 | "consoleHandler": { 14 | "class": "logging.StreamHandler", 15 | "formatter": "demoSimple", 16 | "stream": "ext://sys.stdout" 17 | } 18 | }, 19 | "loggers": { 20 | "root": { 21 | "level": "INFO", 22 | "handlers": ["consoleHandler"], 23 | "propagate": "False" 24 | }, 25 | "test": { 26 | "level": "INFO", 27 | "propagate": "False" 28 | }, 29 | "albow": { 30 | "level": "INFO", 31 | "propagate": "False" 32 | }, 33 | "TestResourceUtility": { 34 | "level": "INFO", 35 | "propagate": "False" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/testresources/TestSound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/test/testresources/TestSound.mp3 -------------------------------------------------------------------------------- /test/testresources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasii2011/albow-python-3/04b9d42705b370b62f0e49d10274eebf3ac54bc1/test/testresources/__init__.py --------------------------------------------------------------------------------