Last active
July 26, 2025 23:55
-
-
Save naranyala/5787fd0d487cec503a2a1d193b3daf91 to your computer and use it in GitHub Desktop.
for the community, a sliding-up drawer (bottom sheets) with backdrop using nim and raylib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import raylib as rl | |
| # === Reactive System with Explicit Type Safety === | |
| type | |
| ObservableWatcher = proc(): void {.closure.} | |
| Observable*[T] = ref object | |
| value*: T | |
| watchers: seq[ObservableWatcher] | |
| proc newObservable*[T](initialValue: T): Observable[T] = | |
| ## Creates a new Observable with explicit initial value and empty watchers | |
| result = Observable[T]( | |
| value: initialValue, | |
| watchers: @[] | |
| ) | |
| proc setValue*[T](observable: Observable[T], newValue: T): void = | |
| ## Sets observable value with change detection and notification | |
| if observable.value != newValue: | |
| observable.value = newValue | |
| for watcher in observable.watchers: | |
| watcher() | |
| proc getValue*[T](observable: Observable[T]): T = | |
| ## Gets current observable value | |
| return observable.value | |
| proc addWatcher*[T](observable: Observable[T], callback: ObservableWatcher): void = | |
| ## Adds a watcher callback to observable | |
| observable.watchers.add(callback) | |
| # === UI Constants with Explicit Types === | |
| const | |
| ScreenWidth: int32 = 400 | |
| ScreenHeight: int32 = 300 | |
| DrawerHeight: float32 = 150.0 | |
| DrawerAnimationSpeed: float32 = 10.0 | |
| BackdropAlpha: uint8 = 150 | |
| TextOffsetX: int32 = 20 | |
| TextOffsetY: int32 = 20 | |
| FontSize: int32 = 20 | |
| ToggleInstructionY: int32 = 40 | |
| ToggleInstructionX: int32 = 60 | |
| DrawerCornerRadius: float32 = 0.1 | |
| DrawerCornerSegments: int32 = 5 | |
| # === Application State with Explicit Initialization === | |
| type | |
| ApplicationState = object | |
| drawerVisible: Observable[bool] | |
| drawerCurrentY: float32 | |
| proc newApplicationState(): ApplicationState = | |
| ## Creates new application state with explicit default values | |
| result = ApplicationState( | |
| drawerVisible: newObservable[bool](false), | |
| drawerCurrentY: float32(ScreenHeight) | |
| ) | |
| var appState: ApplicationState = newApplicationState() | |
| # === Utility Functions === | |
| proc createColor(red: uint8, green: uint8, blue: uint8, alpha: uint8): rl.Color = | |
| ## Creates Color with explicit RGBA values | |
| result = rl.Color(r: red, g: green, b: blue, a: alpha) | |
| proc minFloat32(a: float32, b: float32): float32 = | |
| ## Returns minimum of two float32 values | |
| if a < b: a else: b | |
| proc maxFloat32(a: float32, b: float32): float32 = | |
| ## Returns maximum of two float32 values | |
| if a > b: a else: b | |
| # === UI Component Functions === | |
| proc renderBackdrop(): bool = | |
| ## Renders semi-transparent backdrop and returns true if clicked outside drawer | |
| let backdropColor: rl.Color = createColor(0, 0, 0, BackdropAlpha) | |
| rl.drawRectangle(0, 0, ScreenWidth, ScreenHeight, backdropColor) | |
| let mousePosition: rl.Vector2 = rl.getMousePosition() | |
| let isMouseReleased: bool = rl.isMouseButtonReleased(rl.MouseButton.LEFT) | |
| let drawerBounds = rl.Rectangle( | |
| x: 0.0, | |
| y: appState.drawerCurrentY, | |
| width: float32(ScreenWidth), | |
| height: DrawerHeight | |
| ) | |
| let isClickOutsideDrawer: bool = not rl.checkCollisionPointRec(mousePosition, drawerBounds) | |
| return isMouseReleased and isClickOutsideDrawer | |
| proc updateAndRenderDrawer(): void = | |
| ## Updates drawer animation and renders drawer content | |
| # Calculate target position based on visibility state | |
| let isVisible: bool = getValue(appState.drawerVisible) | |
| let targetY: float32 = if isVisible: | |
| float32(ScreenHeight) - DrawerHeight | |
| else: | |
| float32(ScreenHeight) | |
| # Animate drawer position towards target | |
| if appState.drawerCurrentY < targetY: | |
| appState.drawerCurrentY = minFloat32(appState.drawerCurrentY + DrawerAnimationSpeed, targetY) | |
| elif appState.drawerCurrentY > targetY: | |
| appState.drawerCurrentY = maxFloat32(appState.drawerCurrentY - DrawerAnimationSpeed, targetY) | |
| # Define drawer bounds for rendering | |
| let drawerBounds = rl.Rectangle( | |
| x: 0.0, | |
| y: appState.drawerCurrentY, | |
| width: float32(ScreenWidth), | |
| height: DrawerHeight | |
| ) | |
| # Render drawer background | |
| rl.drawRectangleRounded( | |
| drawerBounds, | |
| DrawerCornerRadius, | |
| DrawerCornerSegments, | |
| rl.DARKGRAY | |
| ) | |
| # Render drawer content text | |
| let textX: int32 = TextOffsetX | |
| let textY: int32 = int32(appState.drawerCurrentY) + TextOffsetY | |
| rl.drawText("Drawer Content", textX, textY, FontSize, rl.WHITE) | |
| proc handleToggleInput(): void = | |
| ## Handles space key input for toggling drawer visibility | |
| if rl.isKeyPressed(rl.KeyboardKey.SPACE): | |
| let currentVisibility: bool = getValue(appState.drawerVisible) | |
| setValue(appState.drawerVisible, not currentVisibility) | |
| proc renderInstructions(): void = | |
| ## Renders toggle instructions | |
| let instructionText: string = "Press SPACE to toggle drawer" | |
| rl.drawText(instructionText, ToggleInstructionX, ToggleInstructionY, FontSize, rl.DARKGRAY) | |
| proc handleBackdropInteraction(): void = | |
| ## Handles backdrop click to close drawer | |
| let isDrawerVisible: bool = getValue(appState.drawerVisible) | |
| if isDrawerVisible and renderBackdrop(): | |
| setValue(appState.drawerVisible, false) | |
| # === Main Application Loop === | |
| proc main(): void = | |
| # Initialize window with explicit dimensions | |
| rl.initWindow(ScreenWidth, ScreenHeight, "Sliding Drawer with Backdrop") | |
| rl.setTargetFPS(60) | |
| # Main rendering and interaction loop | |
| while not rl.windowShouldClose(): | |
| rl.beginDrawing() | |
| rl.clearBackground(rl.RAYWHITE) | |
| # Handle user input | |
| handleToggleInput() | |
| # Render UI instructions | |
| renderInstructions() | |
| # Handle backdrop interaction (only when drawer is visible) | |
| handleBackdropInteraction() | |
| # Update and render drawer | |
| updateAndRenderDrawer() | |
| rl.endDrawing() | |
| # Cleanup resources | |
| rl.closeWindow() | |
| # Entry point with explicit module guard | |
| when isMainModule: | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment