├── examples ├── config.nims ├── white_noise.nim ├── mouse_buttons.nim ├── colorful_patterns.nim ├── square_patterns.nim ├── bubbles_patterns.nim ├── red_square.nim ├── plasma.nim ├── simple_mouse_drawing.nim ├── audio.nim ├── interactive_julia_set.nim ├── conways_game_of_life.nim ├── walkable_carpet.nim ├── audio_bytebeats.nim ├── mandelbrot.nim └── threaded_mandelbrot.nim ├── .gitmodules ├── fenstim.nimble ├── LICENSE ├── .github └── workflows │ └── blank.yml ├── src ├── fenstim_audio.nim └── fenstim.nim └── README.md /examples/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/fensterb"] 2 | path = src/fensterb 3 | url = https://github.com/h3rald/fensterb/ 4 | -------------------------------------------------------------------------------- /examples/white_noise.nim: -------------------------------------------------------------------------------- 1 | import fenstim, random 2 | 3 | var app = init(Fenster, "White noise with fenstim", 800, 600) 4 | 5 | while app.loop and app.keys[27] == 0: 6 | for x in 0 ..< app.width: 7 | for y in 0 ..< app.height: 8 | app.pixel(x, y) = rand(uint32) -------------------------------------------------------------------------------- /fenstim.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "1.0.3" 4 | author = "Comunity" 5 | description = "The most minimal cross-platform GUI library - in Nim" 6 | skipDirs = @["examples"] 7 | license = "MIT" 8 | 9 | # Deps 10 | requires "nim >= 1.0.0" 11 | 12 | srcDir = "src" 13 | -------------------------------------------------------------------------------- /examples/mouse_buttons.nim: -------------------------------------------------------------------------------- 1 | import fenstim, strutils 2 | 3 | var 4 | app = init(Fenster, "Mouse Buttons test", 800, 600) 5 | oldmouse = app.mouse 6 | 7 | while app.loop and app.keys[27] == 0: 8 | if oldmouse != app.mouse: 9 | oldmouse = app.mouse 10 | echo app.mouse 11 | -------------------------------------------------------------------------------- /examples/colorful_patterns.nim: -------------------------------------------------------------------------------- 1 | import fenstim 2 | 3 | var 4 | app = init(Fenster, "Colorful patterns", 320, 240) 5 | t = 1 6 | 7 | while app.loop and app.keys[27] == 0: 8 | t += 1 9 | for i in 0.. 0: 20 | let audio = generateAudio(n) 21 | app_audio.write(audio) 22 | 23 | for i in 0.. 4: return i 19 | 20 | zy = 2*zx*zy + cy 21 | zx = zx2 - zy2 + cx 22 | 23 | return 0 24 | 25 | while app.loop and app.keys[27] == 0: 26 | let (mouseX, mouseY) = app.mouse.pos 27 | if (mouseX, mouseY) != oldpos: 28 | oldpos = (mouseX, mouseY) 29 | cx = mouseX.float32 / app.width.float32 * 4 - 2 30 | cy = mouseY.float32 / app.height.float32 * 4 - 2 31 | 32 | for px in 0.. 0: 47 | app_audio.write(generateAudio(app_audio.available)) 48 | 49 | for i in 0.. 4.0: return i 21 | zy = 2.0 * zx * zy + y 22 | zx = zx2 - zy2 + x 23 | zx2 = zx * zx 24 | zy2 = zy * zy 25 | 26 | return maxIter 27 | 28 | proc findInterestingArea(currX, currY, currZoom: float64): tuple[x, y: float64] = 29 | const searchRadius = 2.0 30 | const samples = 10 31 | var bestX: float64 = currX 32 | var bestY: float64 = currY 33 | var bestScore = 0 34 | 35 | for _ in 0.. 10 and iterations < maxIterations-10: 45 | let score = maxIterations - abs(iterations - maxIterations div 2) 46 | if score > bestScore: 47 | bestScore = score 48 | bestX = x 49 | bestY = y 50 | 51 | if bestScore == 0: 52 | # If no interesting point found, slightly move in a random direction 53 | let angle = rand(0.0..2*PI) 54 | bestX = currX + cos(angle) * (0.1 / currZoom) 55 | bestY = currY + sin(angle) * (0.1 / currZoom) 56 | 57 | return (bestX, bestY) 58 | 59 | proc draw() = 60 | for px in 0.. 0: 75 | self.fps = 1000.0 / elapsedTime.float 76 | 77 | self.lastFrameTime = fenster_time() 78 | result = fenster_loop(self.raw) == 0 79 | 80 | template pixel*(self: Fenster, x, y: int): uint32 = self.raw.buf[y * self.raw.width + x] 81 | template width*(self: Fenster): int = self.raw.width.int 82 | template height*(self: Fenster): int = self.raw.height.int 83 | template keys*(self: Fenster): array[256, cint] = self.raw.keys 84 | template modkey*(self: Fenster): int = self.raw.modkey.int 85 | template mouse*(self: Fenster): tuple[pos: tuple[x, y: int], mclick: array[5, cint], mhold: array[3, cint]] = 86 | ( 87 | pos: (x: self.raw.x.int, y: self.raw.y.int), 88 | mclick: self.raw.mclick, 89 | mhold: self.raw.mhold 90 | ) 91 | proc sleep*(self: Fenster, ms: int) = fenster_sleep(ms.cint) 92 | proc time*(self: Fenster): int64 = fenster_time() 93 | 94 | #Below are functions that are not part of Fenster 95 | template clear*(self: Fenster) = zeroMem(self.raw.buf, self.raw.width.int * self.raw.height.int * sizeof(uint32)) 96 | proc getFonts*(self: Fenster): seq[string] = 97 | let searchPatterns = when defined(linux): 98 | @[ 99 | expandTilde("~/.local/share/fonts/**/*.ttf"), 100 | expandTilde("~/.fonts/**/*.ttf"), 101 | "/usr/*/fonts/**/*.ttf", 102 | "/usr/*/*/fonts/**/*.ttf", 103 | "/usr/*/*/*/fonts/**/*.ttf", 104 | "/usr/*/*/*/*/fonts/**/*.ttf" 105 | ] 106 | elif defined(macosx): 107 | @[ 108 | expandTilde("~/Library/Fonts/**/*.ttf"), 109 | "/Library/Fonts/**/*.ttf", 110 | "/System/Library/Fonts/**/*.ttf", 111 | "/Network/Library/Fonts/**/*.ttf" 112 | ] 113 | elif defined(windows): 114 | @[ 115 | getEnv("SYSTEMROOT") & r"\Fonts\*.ttf", 116 | getEnv("LOCALAPPDATA") & r"\Microsoft\Windows\Fonts\*.ttf" 117 | ] 118 | else: 119 | @[] 120 | 121 | result = newSeq[string]() 122 | for pattern in searchPatterns: 123 | for entry in walkPattern(pattern): 124 | result.add(entry) 125 | -------------------------------------------------------------------------------- /examples/threaded_mandelbrot.nim: -------------------------------------------------------------------------------- 1 | import fenstim, math, random, parsecfg, atomics, parseutils, strutils, os 2 | 3 | randomize() 4 | 5 | const CONFIG_FILE = "config.ini" 6 | const DEFAULT_CONFIG = """ 7 | width=800 8 | height=600 9 | targetFps=60 10 | threadcount=2 11 | """ 12 | 13 | proc createDefaultConfig() = 14 | writeFile(CONFIG_FILE, DEFAULT_CONFIG) 15 | 16 | if not fileExists(CONFIG_FILE): 17 | createDefaultConfig() 18 | 19 | let dict = loadConfig(CONFIG_FILE) 20 | 21 | proc getConfigValue(key: string, default: int): int = 22 | let value = dict.getSectionValue("", key) 23 | if value == "": default else: parseInt(value) 24 | 25 | var 26 | WIDTH = getConfigValue("width", 800) 27 | HEIGHT = getConfigValue("height", 600) 28 | THREAD_COUNT = getConfigValue("threadcount", 2) 29 | TARGETFPS = getConfigValue("targetFps", 60) 30 | 31 | type 32 | ThreadArg = tuple[startY, endY: int] 33 | 34 | echo "Threads: ", THREAD_COUNT, " ", WIDTH, "x", HEIGHT 35 | var 36 | app = init(Fenster, "Multi-threaded Edge-focusing Mandelbrot Set", WIDTH, HEIGHT, TARGETFPS) 37 | centerX, centerY: float64 = 0.0 38 | zoom: float64 = 1.0 39 | maxIterations: Atomic[int] 40 | zoomSpeed: float64 = 1.02 41 | targetX, targetY: float64 = 0.0 42 | 43 | var buffer: seq[uint32] 44 | buffer.setLen(WIDTH * HEIGHT) 45 | 46 | maxIterations.store(100) 47 | 48 | proc mandelbrot(cx, cy: float64, maxIter: int): int {.inline.} = 49 | var 50 | zx, zy, zx2, zy2: float64 = 0.0 51 | x = cx 52 | y = cy 53 | 54 | for i in 1..maxIter: 55 | if zx2 + zy2 > 4.0: return i 56 | zy = 2.0 * zx * zy + y 57 | zx = zx2 - zy2 + x 58 | zx2 = zx * zx 59 | zy2 = zy * zy 60 | 61 | return maxIter 62 | 63 | proc findInterestingArea(currX, currY, currZoom: float64): tuple[x, y: float64] = 64 | const searchRadius = 2.0 65 | const samples = 10 66 | var bestX: float64 = currX 67 | var bestY: float64 = currY 68 | var bestScore = 0 69 | 70 | for _ in 0.. 10 and iterations < maxIterations.load-10: 79 | let score = maxIterations.load - abs(iterations - maxIterations.load div 2) 80 | if score > bestScore: 81 | bestScore = score 82 | bestX = x 83 | bestY = y 84 | 85 | if bestScore == 0: 86 | let angle = rand(0.0..2*PI) 87 | bestX = currX + cos(angle) * (0.1 / currZoom) 88 | bestY = currY + sin(angle) * (0.1 / currZoom) 89 | 90 | return (bestX, bestY) 91 | 92 | proc drawSection(arg: ThreadArg) {.thread.} = 93 | let (startY, endY) = arg 94 | let maxIter = maxIterations.load 95 | 96 | {.cast(gcsafe).}: 97 | for py in startY..