Examples
Source section: `https://appsudo.com/doc#examples`
Example set in the docs
- Todo List App
- Weather App
- Tetris Game
Todo List App
- Demonstrates `DataStore`, `RepeatView`, `find_element_by_id(...)`, and form handlers.
Python excerpt:
```python
class TodoListApp(App):
def __init__(self, application_path):
App.__init__(self, "TodoList", application_path)
self.inflater = ViewInflater(self)
self.ui_view = None
self.tasks = []
self.data = TodoListData(application_path)
self.setup()
def setup(self):
DataStore.get("todo_tasks", self.load_tasks)
def on_create_task_view(self, parent):
return self.inflater.inflate("todo_item", None)
def on_bind_task_view(self, view, task):
checkbox = view.find_element_by_id("task_checkbox")
checkbox.set_text(task.text)
checkbox.set_checked(task.completed)
```
ASXML excerpt:
```xml
```
Weather App
- Demonstrates `HttpClient`, `DataLoader`, `DataStore`, and live UI updates.
Python excerpt:
```python
from runtime.DataLoader import DataLoader
config = DataLoader.load_json(application_path, "config")
self.api_key = config.get("weather_api_key", "YOUR_API_KEY_HERE")
url = f"https://api.openweathermap.org/data/2.5/weather?q={self.data.city}&units=metric&appid={self.api_key}"
HttpClient.current().get(url, {}, self.process_weather_response)
```
Config file:
```json
{
"weather_api_key": "YOUR_OPENWEATHERMAP_API_KEY"
}
```
Tetris Game
- Demonstrates `Canvas`, `GameWindow`, and pygame integration.
Python excerpt:
```python
class TetrisApp(App):
def __init__(self, application_path):
super().__init__("TetrisGameApp", application_path)
self.application_path = application_path
self.ui_view = None
self.game_instance = None
self.setup()
def setup(self):
self.ui_view = Canvas()
self.game_instance = TetrisGamePyGame(self.application_path, self.ui_view)
def run(self):
super().run()
if self.ui_view:
self.render(self.ui_view.render())
```
Code Snippets From Docs
- The blocks below were extracted from the corresponding section of `appsudo.com/doc`.
Snippet 1
```
from appsudo import App, AppRuntime
from components.view import ViewInflater
from components.form import Button, TextBox
from runtime.Helper import Helper
from runtime.DataStore import DataStore
class TodoItem:
def __init__(self, text, completed=False):
self.text = text
self.completed = completed
class TodoListData:
def __init__(self, application_path):
self.application_path = application_path
self.new_task = ""
self.task_placeholder = "Enter a new task..."
self.add_button_text = "Add Task"
self.title = "My Todo List"
self.list = [] # For storing the tasks
class TodoListApp(App):
def __init__(self, application_path):
App.__init__(self, "TodoList", application_path)
self.inflater = ViewInflater(self)
self.ui_view = None
self.tasks = []
self.data = TodoListData(application_path)
self.setup()
def setup(self):
# Load saved tasks from DataStore
DataStore.get("todo_tasks", self.load_tasks)
def load_tasks(self, saved_tasks):
if saved_tasks:
try:
import json
task_data = json.loads(saved_tasks)
for item in task_data:
self.tasks.append(TodoItem(item["text"], item["completed"]))
except Exception as e:
Helper.log(f"Error loading tasks: {e}")
# Create UI
self.ui_view = self.inflater.inflate("todo_list", self.data)
self.refresh_task_list()
def save_tasks(self):
# Save tasks to DataStore
try:
import json
task_data = []
for task in self.tasks:
task_data.append({
"text": task.text,
"completed": task.completed
})
DataStore.set("todo_tasks", json.dumps(task_data), lambda r: None)
except Exception as e:
Helper.log(f"Error saving tasks: {e}")
def on_task_input_change(self, element):
self.data.new_task = element.get_text()
def on_add_task(self, element):
if self.data.new_task.strip():
# Add new task
self.tasks.append(TodoItem(self.data.new_task))
# Clear input
input_box = self.ui_view.find_element_by_id("task_input")
input_box.set_text("")
self.data.new_task = ""
# Refresh list and save
self.refresh_task_list()
self.save_tasks()
def on_create_task_view(self, parent):
# Create a view for each task
return self.inflater.inflate("todo_item", None)
def on_bind_task_view(self, view, task):
# Update task item view with data
checkbox = view.find_element_by_id("task_checkbox")
checkbox.set_text(task.text)
checkbox.set_checked(task.completed)
# Set delete button handler
delete_btn = view.find_element_by_id("delete_button")
delete_btn.set_tag(self.tasks.index(task)) # Store task index in button tag
# Set checkbox change handler
checkbox.set_tag(self.tasks.index(task)) # Store task index in checkbox tag
def on_task_status_change(self, element, checked):
# Update task completion status
task_index = element.get_tag()
if 0 <= task_index < len(self.tasks):
self.tasks[task_index].completed = checked
self.save_tasks()
def on_delete_task(self, element):
# Delete task
task_index = element.get_tag()
if 0 <= task_index < len(self.tasks):
del self.tasks[task_index]
self.refresh_task_list()
self.save_tasks()
def refresh_task_list(self):
# Update task list view
task_list_scroll_view = self.ui_view.find_element_by_id("task_list")
if task_list_scroll_view:
repeat_view_container = task_list_scroll_view.get_child_at(0) # Assuming RepeatView is the direct child
if repeat_view_container: # This might be the RepeatView itself or a wrapper
# If RepeatView is not the direct child, find it
tasks_repeat_view = repeat_view_container.find_element_by_id("tasks_repeat_view") or repeat_view_container
if tasks_repeat_view and hasattr(tasks_repeat_view, 'clear'):
tasks_repeat_view.clear() # Clear items from RepeatView if it has clear method
elif tasks_repeat_view and hasattr(tasks_repeat_view, 'set_items'): # Or reset items
tasks_repeat_view.set_items([])
# Update data
self.data.list = self.tasks
# Rebind data to RepeatView
repeat_view = self.ui_view.find_element_by_id("tasks_repeat_view")
if repeat_view:
repeat_view.set_items(self.tasks)
def run(self):
App.run(self)
self.render(self.ui_view.render())
```
Snippet 2
```
```
Snippet 3
```
```
Snippet 4
```
from appsudo import App, AppRuntime
from components.view import ViewInflater
from components.form import Button, TextBox, Label, Progress
from runtime import HttpClient
from runtime.Helper import Helper
from runtime.DataStore import DataStore
class WeatherData:
def __init__(self, application_path):
self.application_path = application_path
self.city = ""
self.temperature = "-- °C"
self.description = "Enter a city to get weather information"
self.humidity = "Humidity: -- %"
self.wind_speed = "Wind: -- km/h"
self.placeholder = "Enter city name..."
self.button_text = "Get Weather"
self.title = "Weather App"
self.loading = False
self.error = ""
class WeatherApp(App):
def __init__(self, application_path):
App.__init__(self, "WeatherApp", application_path)
self.inflater = ViewInflater(self)
self.ui_view = None
self.data = WeatherData(application_path)
from runtime.DataLoader import DataLoader
config = DataLoader.load_json(application_path, "config")
self.api_key = config.get("weather_api_key", "YOUR_API_KEY_HERE") # Ensure this key is valid
self.setup()
def setup(self):
DataStore.get("last_city", self.load_last_city)
def load_last_city(self, city):
if city:
self.data.city = city
self.ui_view = self.inflater.inflate("weather_view", self.data)
if self.data.city:
self.fetch_weather()
else: # Ensure UI is updated even if no last city
self.update_ui()
def on_city_input_change(self, element):
self.data.city = element.get_text()
def on_get_weather(self, element):
if not self.data.city.strip():
self.data.error = "Please enter a city name"
self.update_ui()
return
self.fetch_weather()
def fetch_weather(self):
self.data.loading = True
self.data.error = ""
self.update_ui()
url = f"https://api.openweathermap.org/data/2.5/weather?q={self.data.city}&units=metric&appid={self.api_key}"
HttpClient.current().get(url, {}, self.process_weather_response)
def process_weather_response(self, response):
self.data.loading = False
try:
import json
weather_data = json.loads(response)
if weather_data.get("cod") != 200:
self.data.error = weather_data.get("message", "Error fetching weather data")
# Clear previous weather data on error
self.data.temperature = "-- °C"
self.data.description = "Could not fetch weather."
self.data.humidity = "Humidity: -- %"
self.data.wind_speed = "Wind: -- km/h"
else:
self.data.temperature = f"{round(weather_data['main']['temp'])} °C"
self.data.description = weather_data['weather'][0]['description'].capitalize()
self.data.humidity = f"Humidity: {weather_data['main']['humidity']}%"
self.data.wind_speed = f"Wind: {round(weather_data['wind']['speed'] * 3.6)} km/h" # Convert m/s to km/h
self.data.error = "" # Clear error on success
DataStore.set("last_city", self.data.city, lambda r: None)
except Exception as e:
self.data.error = f"Error processing data: {str(e)}"
Helper.log(f"Weather API error: {e}")
# Clear previous weather data on error
self.data.temperature = "-- °C"
self.data.description = "Error processing data."
self.data.humidity = "Humidity: -- %"
self.data.wind_speed = "Wind: -- km/h"
self.update_ui()
def update_ui(self):
if not self.ui_view: return
elements_to_update = {
"temperature_label": self.data.temperature,
"description_label": self.data.description,
"humidity_label": self.data.humidity,
"wind_label": self.data.wind_speed,
"error_label": self.data.error
}
for el_id, text_val in elements_to_update.items():
element = self.ui_view.find_element_by_id(el_id)
if element: element.set_text(text_val)
loading_indicator = self.ui_view.find_element_by_id("loading_indicator")
if loading_indicator:
loading_indicator.set_indeterminate(self.data.loading)
if not self.data.loading : loading_indicator.set_progress(0)
def run(self):
App.run(self)
if self.ui_view:
self.render(self.ui_view.render())
```
Snippet 5
```
```
Snippet 6
```
{
"weather_api_key": "YOUR_OPENWEATHERMAP_API_KEY"
}
```
Snippet 7
```
from appsudo import App
from components.canvas import Canvas
from components.extras.game.window import GameWindow
import pygame
import random
import sys
class Colors:
DARK_BLUE = (44, 62, 80)
LIGHT_BLUE = (52, 152, 219)
WHITE = (255, 255, 255)
GREEN = (46, 204, 113)
PURPLE = (142, 68, 173)
ORANGE = (230, 126, 34)
YELLOW = (241, 196, 15)
RED = (231, 76, 60)
CYAN = (0, 255, 255)
BLOCK_COLORS = [CYAN, YELLOW, PURPLE, GREEN, RED, ORANGE, LIGHT_BLUE]
@classmethod
def get_color_tuple(cls, idx):
return cls.BLOCK_COLORS[idx % len(cls.BLOCK_COLORS)]
SHAPES = [
[[1, 1, 1, 1]], # I
[[1, 1], [1, 1]], # O
[[0, 1, 0], [1, 1, 1]], # T
[[1, 0, 0], [1, 1, 1]], # L
[[0, 0, 1], [1, 1, 1]], # J
[[0, 1, 1], [1, 1, 0]], # S
[[1, 1, 0], [0, 1, 1]] # Z
]
class Piece:
def __init__(self, x, y, shape_idx):
self.x = x
self.y = y
self.shape_idx = shape_idx
self.shape = SHAPES[shape_idx]
self.color_idx = shape_idx
def rotate(self):
self.shape = [list(row) for row in zip(*self.shape[::-1])]
class TetrisGameLogic:
def __init__(self, cols=10, rows=20):
self.cols = cols
self.rows = rows
self.grid = [[0 for _ in range(cols)] for _ in range(rows)]
self.current_piece = self.new_piece()
self.next_piece = self.new_piece()
self.score = 0
self.level = 1
self.lines_cleared_total = 0
self.game_over = False
self.fall_speed_initial = 500
self.fall_speed = self.fall_speed_initial
self.fall_time = 0
def new_piece(self):
shape_idx = random.randint(0, len(SHAPES) - 1)
start_x = self.cols // 2 - len(SHAPES[shape_idx][0]) // 2
return Piece(start_x, 0, shape_idx)
def is_valid_position(self, piece, offset_x=0, offset_y=0):
for r_idx, row in enumerate(piece.shape):
for c_idx, cell in enumerate(row):
if cell:
x = piece.x + c_idx + offset_x
y = piece.y + r_idx + offset_y
if not (0 <= x < self.cols and 0 <= y < self.rows and self.grid[y][x] == 0):
return False
return True
def lock_piece(self):
for r_idx, row in enumerate(self.current_piece.shape):
for c_idx, cell in enumerate(row):
if cell:
# Ensure y+r_idx is within grid bounds before assignment
if 0 <= self.current_piece.y + r_idx < self.rows and 0 <= self.current_piece.x + c_idx < self.cols:
self.grid[self.current_piece.y + r_idx][self.current_piece.x + c_idx] = self.current_piece.color_idx + 1
self.clear_lines()
self.current_piece = self.next_piece
self.next_piece = self.new_piece()
if not self.is_valid_position(self.current_piece):
self.game_over = True
def clear_lines(self):
lines_cleared_this_turn = 0
new_grid = [row for row in self.grid if not all(cell > 0 for cell in row)] # Check for filled cells
lines_cleared_this_turn = self.rows - len(new_grid)
if lines_cleared_this_turn > 0:
self.lines_cleared_total += lines_cleared_this_turn
for _ in range(lines_cleared_this_turn):
new_grid.insert(0, [0 for _ in range(self.cols)])
self.grid = new_grid
score_map = {1: 40, 2: 100, 3: 300, 4: 1200}
self.score += score_map.get(lines_cleared_this_turn, 0) * self.level
self.level = (self.lines_cleared_total // 10) + 1
self.fall_speed = max(100, self.fall_speed_initial - (self.level - 1) * 20)
def move(self, dx, dy):
if not self.game_over and self.is_valid_position(self.current_piece, dx, dy):
self.current_piece.x += dx
self.current_piece.y += dy
return True
return False
def drop(self):
if not self.game_over:
# Keep moving down as long as it's a valid move
while self.is_valid_position(self.current_piece, 0, 1):
self.current_piece.y += 1
self.score += 1
self.lock_piece()
def rotate_piece(self):
if not self.game_over:
original_shape = [list(row) for row in self.current_piece.shape]
self.current_piece.rotate()
if not self.is_valid_position(self.current_piece):
self.current_piece.shape = original_shape
def update(self, dt_ms):
if self.game_over:
return
self.fall_time += dt_ms
if self.fall_time >= self.fall_speed:
self.fall_time = 0
if not self.move(0, 1):
self.lock_piece()
def reset_game(self):
self.grid = [[0 for _ in range(self.cols)] for _ in range(self.rows)]
self.current_piece = self.new_piece()
self.next_piece = self.new_piece()
self.score = 0
self.level = 1
self.lines_cleared_total = 0
self.game_over = False
self.fall_speed = self.fall_speed_initial
self.fall_time = 0
class TetrisGamePyGame(GameWindow):
def __init__(self, application_path, canvas_view):
super().__init__(canvas_view)
pygame.init()
self.cell_size = 25
self.game_logic = TetrisGameLogic()
self.grid_width_px = self.game_logic.cols * self.cell_size
self.grid_height_px = self.game_logic.rows * self.cell_size
self.info_panel_width = 150
self.screen_width = self.grid_width_px + self.info_panel_width + 30
self.screen_height = self.grid_height_px + 40
# This surface is managed by GameWindow, do not re-set_mode here
# self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
# pygame.display.set_caption("AppSudo Tetris") # Caption is also handled by GameWindow
self.font_small = pygame.font.Font(None, 24)
self.font_large = pygame.font.Font(None, 36)
self.font_game_over = pygame.font.Font(None, 48)
self.clock = pygame.time.Clock()
# self.last_fall_time = pygame.time.get_ticks() # Replaced by game_logic.fall_time and dt
def on_draw(self, surface):
dt = self.clock.tick(60) # Get delta time in milliseconds
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False # Signal GameWindow to stop
return # Exit on_draw early
if event.type == pygame.KEYDOWN:
if self.game_logic.game_over:
if event.key == pygame.K_r:
self.game_logic.reset_game()
else:
if event.key == pygame.K_LEFT:
self.game_logic.move(-1, 0)
elif event.key == pygame.K_RIGHT:
self.game_logic.move(1, 0)
elif event.key == pygame.K_DOWN:
if self.game_logic.move(0, 1):
self.game_logic.score +=1
else: # If cannot move down, lock piece
self.game_logic.lock_piece()
self.game_logic.fall_time = 0 # Reset fall timer on manual down
elif event.key == pygame.K_UP:
self.game_logic.rotate_piece()
elif event.key == pygame.K_SPACE:
self.game_logic.drop()
if not self.game_logic.game_over:
self.game_logic.update(dt)
surface.fill(Colors.DARK_BLUE)
grid_origin_x = 15
grid_origin_y = 20
pygame.draw.rect(surface, Colors.LIGHT_BLUE,
(grid_origin_x - 2, grid_origin_y - 2,
self.grid_width_px + 4, self.grid_height_px + 4), 2)
for r_idx, row in enumerate(self.game_logic.grid):
for c_idx, cell_color_idx in enumerate(row):
if cell_color_idx > 0:
color = Colors.get_color_tuple(cell_color_idx - 1)
pygame.draw.rect(surface, color,
(grid_origin_x + c_idx * self.cell_size,
grid_origin_y + r_idx * self.cell_size,
self.cell_size -1, self.cell_size -1))
if not self.game_logic.game_over and self.game_logic.current_piece:
piece = self.game_logic.current_piece
for r_idx, row_data in enumerate(piece.shape):
for c_idx, cell in enumerate(row_data):
if cell:
color = Colors.get_color_tuple(piece.color_idx)
pygame.draw.rect(surface, color,
(grid_origin_x + (piece.x + c_idx) * self.cell_size,
grid_origin_y + (piece.y + r_idx) * self.cell_size,
self.cell_size -1, self.cell_size-1))
info_x = grid_origin_x + self.grid_width_px + 20
score_text = self.font_large.render(f"Score: {self.game_logic.score}", True, Colors.WHITE)
surface.blit(score_text, (info_x, grid_origin_y + 20))
level_text = self.font_small.render(f"Level: {self.game_logic.level}", True, Colors.WHITE)
surface.blit(level_text, (info_x, grid_origin_y + 70))
lines_text = self.font_small.render(f"Lines: {self.game_logic.lines_cleared_total}", True, Colors.WHITE)
surface.blit(lines_text, (info_x, grid_origin_y + 100))
next_text = self.font_small.render("Next:", True, Colors.WHITE)
surface.blit(next_text, (info_x, grid_origin_y + 150))
if self.game_logic.next_piece:
next_p = self.game_logic.next_piece
for r_idx, row_data in enumerate(next_p.shape):
for c_idx, cell in enumerate(row_data):
if cell:
color = Colors.get_color_tuple(next_p.color_idx)
pygame.draw.rect(surface, color,
(info_x + c_idx * self.cell_size,
grid_origin_y + 180 + r_idx * self.cell_size,
self.cell_size -1, self.cell_size -1))
if self.game_logic.game_over:
game_over_text = self.font_game_over.render("GAME OVER", True, Colors.RED)
text_rect = game_over_text.get_rect(center=(self.screen_width // 2, self.screen_height // 2 - 50))
surface.blit(game_over_text, text_rect)
restart_text = self.font_small.render("Press 'R' to Restart", True, Colors.WHITE)
restart_rect = restart_text.get_rect(center=(self.screen_width // 2, self.screen_height // 2))
surface.blit(restart_text, restart_rect)
# pygame.display.flip() # GameWindow handles this via canvas update
class TetrisApp(App):
def __init__(self, application_path):
super().__init__("TetrisGameApp", application_path)
self.application_path = application_path
self.ui_view = None
self.game_instance = None
self.setup()
def setup(self):
self.ui_view = Canvas()
self.game_instance = TetrisGamePyGame(self.application_path, self.ui_view)
# GameWindow's on_draw is automatically called by the Canvas component
# when Appsudo renders it.
def run(self):
super().run()
if self.ui_view:
self.render(self.ui_view.render())
def on_key_event(self, key_code, is_pressed): # Appsudo key event hook
if self.game_instance and hasattr(self.game_instance, 'handle_key_event'):
self.game_instance.handle_key_event(key_code, is_pressed)
return True # Indicate event was handled (optional)
def on_close(self):
if self.game_instance:
self.game_instance.running = False
pygame.quit()
super().on_close()
```
Detailed Coverage Map
- The entries below were derived from the section structure in `appsudo.com/doc`.
Articles
- `example-todo`: Todo List App
- `example-weather`: Weather App
- `example-tetris`: Tetris Game