←back to thread

511 points ayaros | 1 comments | | HN request time: 0.267s | source

https://lisagui.com/info.html

This is a web OS I wrote in vanilla JS that looks like the Apple Lisa Office System (1983-85), with other contemporaneous influences and additional improvements and features. It's currently in alpha and isn't remotely bug free. I had been holding off on posting this here until it was somewhat presentable and useful. Please note; the Lisa conforms more literally to the desktop metaphor than most modern GUIs - some of the important differences are mentioned in the readme.

This is a complete recreation of the UI in JS; it all renders to a single canvas element. It's not a CSS theme, and not an emulator ported to JS. None of the code is written by Apple. I'll be happy to elaborate more in the comments, but the short version is the entire UI is defined outside the DOM using JS objects. Thus, every interface element - menus, windows, controls, and even typefaces - was recreated from scratch. There are no font files - I wrote my own typesetting system, which supports combining multiple text styles and generates new glyph variants on the fly.

Many of the technical decisions I made were motivated by a desire to have this look the same in every browser. That's harder to do with the DOM and CSS, and why I moved as much logic as I could to JS. Also, the only part of the project outside of vanilla JS and standard web APIs is the Gulp toolkit, which I'm using as a minification/build tool. No vibe coding was used to make this!

This is based on a UI from the 80s, and won't work well on your phone. If you insist on running it that way, turn on trackpad mode in the touchscreen settings panel of the preferences app. For best results, install it as a PWA (add it to your home screen). Also there are some odd Android bugs; the native touchscreen keyboard is currently broken, and there's an issue with the cursor when dragging windows.

I realize there's not a whole lot to do within LisaGUI right now; I've got a big list of additional features and apps I'll be adding in the future. I've been working on this project for a while, and I'm eager to hear people's feedback and answer questions about it.

Show context
ayaros ◴[] No.44483280[source]
The Lisa doesn't have square pixels, so the canvas is scaled to be 1.5x as high as it is wide. This generally looks fine on high-dpi displays, because there's technically twice as much space to render with (pixels are 2px wide by 3px high). However, things will look distorted on a lower resolution display (where pixels are 1px wide by 1.5px high). That's just a compromise I made when designing this.

The good news is, if you have a large enough low-dpi display, and you make the window big enough, the automatic integer scaling settings will kick in, and the pixels themselves will be displayed larger. This can be forced via the preferences app (under the display options). If you screw this up, then restart LisaGUI while holding the shift key to reset the scaling settings.

EDIT: Unrelated to this, there are a couple minor bugs with PWAs on iOS relating to the positioning of the canvas. These can be resolved by rotating your device to a different orientation and then rotating it back to the original position... but this is annoying.

EDIT 2: To close windows, just double click the icon in the titlebar! This "collapses the window back into an icon."

replies(1): >>44484710 #
ivape ◴[] No.44484710[source]
How do you handle dynamic window/font scaling regardless of browser size (you get it for free with html mostly).
replies(2): >>44484971 #>>44494279 #
ayaros ◴[] No.44484971[source]
It's integer scaling; it involves changing the width, height, and style attributes of the canvas dynamically. I have a whole class that handles this, and let me tell you it took a lot of effort to get working properly and involved juggling around quite a few parameters, including the DPI, the border width, the pixel aspect ratio, and more. I had to use a ResizeObserver object to detect changes in the size of the DOM's body element.

To get the canvas to be consistently smooth, I had to apply a lot of contrast using a CSS filter, and I set image-rendering to pixelated, IIRC.

replies(1): >>44491670 #
Gormo ◴[] No.44491670[source]
It might be worthwhile to overshoot the target resolution significantly with the nearest-neighbor scaler, then downscale interpolatively (e.g. bilinear) to the viewport. This should help dealing with doubled rows/columns and moire patterns, while maintaining visually crisp pixels.

I do something similar when dealing with raw screenshots from DOS games, which are typically 320x200, but intended for 4:3 display, so pixels are 5:6. In this case, upscaling to 1600x1200 (the smallest possible integer upscale to achieve the correct aspect ratio) with nearest-neighbor, then downscaling with bilinear to the target res (e.g. 1440x1080, 960x720, etc.) works really well.

replies(1): >>44491989 #
1. Dwedit ◴[] No.44491989[source]
Not only do you upscale with nearest neighbor then downscale with bilinear, you also make sure that you are using the correct colorspace for downscaling.

The most common mistake is to reinterpret sRGB as if it is a linear color space, and assume that half of RGB 255,255,255 is RGB 128,128,128. But that's wrong. sRGB is a gamma-compressed color space that does not respond linearly.

First you treat all sRGB values as if they are between 0 and 1. To convert into linear RGB, take all values to the power of 2.2 (like squaring them). Then you do any math on color values in Linear RGB. For example, half of linear 1 is indeed 0.5. To convert back into sRGB, take all values to the power of 1/2.2 (like a square root).'

This means that 0.5 in linear RGB corresponds to 0.73 in sRGB. You can confirm this by placing a black and white checkerboard next to a square of RGB 186,186,186. They will match in apparent intensity (provided you have an sRGB display that doesn't have viewing angle problems).

Some graphics APIs can do the sRGB to Linear RGB conversion for you automatically, but you need to request the API to do that. This makes the pixel shader only see Linear RGB, but the pixels are coming from an sRGB texture, and are being written to an sRGB buffer as output.