Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8dffd62
Add a themeable XP Paint application
Cyanoxide Jun 14, 2026
e284595
Match the authentic XP Paint layout
Cyanoxide Jun 14, 2026
e43ec4a
Make canvas handles functional, fix the status grip, use XP scrollbars
Cyanoxide Jun 14, 2026
e475dac
Use the XP tool spritemap for the Paint toolbox icons
Cyanoxide Jun 14, 2026
644428b
Centre all tool icons, shrink canvas handles, fix horizontal scroll
Cyanoxide Jun 14, 2026
64610bc
Functional selection tools, footer/handle/maximize fixes
Cyanoxide Jun 14, 2026
19817bd
Implement the polygon and magnifier (zoom) tools
Cyanoxide Jun 14, 2026
30ce3d7
Polish polygon and zoom interactions
Cyanoxide Jun 14, 2026
2e9c0d6
Polygon supports click-drag edges as well as click-to-click
Cyanoxide Jun 14, 2026
fe5d22e
Implement the Text and Curve tools
Cyanoxide Jun 14, 2026
6cf28cb
Keep the polygon when switching tools instead of erasing it
Cyanoxide Jun 14, 2026
bc6eef3
Build out per-tool options and the behaviours behind them
Cyanoxide Jun 14, 2026
40e6086
Fit the tool options inside the box; centre spray; white zoom square
Cyanoxide Jun 14, 2026
17e68f6
Make the options box a fixed size and fit every tool's options in it
Cyanoxide Jun 14, 2026
521c3e4
Evenly space the eraser size options
Cyanoxide Jun 14, 2026
9f9b75f
Keep equal spacing between the eraser squares
Cyanoxide Jun 14, 2026
0bf607e
Fit the zoom options and make the selection fill the row width
Cyanoxide Jun 14, 2026
48d4a2d
Vary the zoom squares by level; fit the rows; fill the selection width
Cyanoxide Jun 14, 2026
1a25e08
Fill the zoom rows edge-to-edge; square on the right; centred rows
Cyanoxide Jun 14, 2026
53fb764
Give the brush all 12 nibs (L/M/S diagonals both ways)
Cyanoxide Jun 14, 2026
6fb2b6c
Keep equal spacing between the line/curve thickness options
Cyanoxide Jun 14, 2026
658ee13
Blue selection with white icons for brush/spray/shape options
Cyanoxide Jun 14, 2026
6237da5
Theme-match the option selection, edge-to-edge shapes, drop hover
Cyanoxide Jun 14, 2026
6755084
Label Paint as "Paint" in the launchers; remove it from Startup
Cyanoxide Jun 14, 2026
0c54485
Add the full Paint menu bar (all options disabled)
Cyanoxide Jun 15, 2026
8b92acb
Wire Paint's File menu: Exit, Save (to desktop), Save to Computer
Cyanoxide Jun 15, 2026
16320f9
Add monochrome tool-icon cursors on the Paint canvas
Cyanoxide Jun 15, 2026
34ffcb6
Let saved desktop images be marquee-selected and recycled
Cyanoxide Jun 15, 2026
1ad7731
Derive Paint cursors from the spritemap and stop them blurring when z…
Cyanoxide Jun 15, 2026
9260e85
Make recycled saved images restorable instead of deleting them
Cyanoxide Jun 15, 2026
ba581d6
Fit saved-image thumbnails tightly, removing the white bands
Cyanoxide Jun 15, 2026
24773d0
Fix the curve tool for click-to-point line drawing
Cyanoxide Jun 15, 2026
d4be217
Drop the grey border from saved-image desktop icons
Cyanoxide Jun 15, 2026
d93ea8f
Update Readme files
Cyanoxide Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@ An authentic recreation of Windows XP, created using **React** and **Typescript*
- Functional Taskbar and Start Menu
- Movable Desktop Icons
- Movable & resizable windows
- Login Screen
- Login, Shutdown and boot up sequences
- **Notepad**
- Browsable **File Explorer** with back and forward functionality
- **File Explorer**
- **Internet Explorer** with Wayback Machine implementation for period accurate web browsing.
- **Run** can open applications and Folders, either by internal appId, or title. URLs will also open in Internet Explorer.
- **[Solitaire](https://github.com/Cyanoxide/react-solitaire)**
- **Clippy**, the desktop assistant — drag him around, ask him questions, I can't guarentee he will be much help though.
- Login, Shutdown and boot up sequences
- **Paint**
- **Clippy**

## Roadmap

This is an ongoing project, with many more features I’d like to include in the future, here are some potential features I make look into:

- MSN Messenger
- MS Paint
- Minesweeper
- Doom?
- Media Player
- Picture Viewer

## Demo

Expand Down
Binary file added frontend/public/icon__paint--large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/spritemap__paint-tools.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 18 additions & 2 deletions frontend/src/components/Applications/FileExplorer/FileExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const Applications = applicationsJSON as unknown as Record<string, Application>;
const Files = filesJSON as unknown as Record<string, string[] | File[]>;

const FileExplorer = ({ appId }: Record<string, string>) => {
const { currentWindows, recycledItems, dispatch } = useContext();
const { currentWindows, recycledItems, savedImages, dispatch } = useContext();
const recycledImages = savedImages.filter((image) => image.recycled);
const [selectedItem, setSelectedItem] = useState<string | null>(null);
const [isBackDisabled, setIsBackDisabled] = useState(true);
const [isForwardDisabled, setIsForwardDisabled] = useState(true);
Expand All @@ -36,6 +37,12 @@ const FileExplorer = ({ appId }: Record<string, string>) => {

const emptyRecycleBinHandler = () => {
dispatch({ type: "SET_RECYCLED_ITEMS", payload: [] });
if (recycledImages.length) dispatch({ type: "SET_SAVED_IMAGES", payload: savedImages.map((image) => image.recycled ? { ...image, recycled: false } : image) });
playSound("recycle", true);
};

const restoreSavedImageHandler = (id: string) => {
dispatch({ type: "SET_SAVED_IMAGES", payload: savedImages.map((image) => image.id === id ? { ...image, recycled: false } : image) });
playSound("recycle", true);
};

Expand Down Expand Up @@ -194,7 +201,7 @@ const FileExplorer = ({ appId }: Record<string, string>) => {
<CollapseBox title="Recycle Bin Tasks">
<ul className="flex flex-col gap-2 p-3">
<li>
<button className="flex items-center" onClick={emptyRecycleBinHandler} disabled={!recycledItems.length}>
<button className="flex items-center" onClick={emptyRecycleBinHandler} disabled={!recycledItems.length && !recycledImages.length}>
<img src="/icon__recycle_bin.png" className="mr-2" width="12" height="12" />
<p>Empty the Recycle Bin</p>
</button>
Expand Down Expand Up @@ -270,6 +277,15 @@ const FileExplorer = ({ appId }: Record<string, string>) => {
</button>
);
})}
{appId === "recycleBin" && recycledImages.map((image) => {
const isActive = (selectedItem === image.id);
return (
<button key={image.id} data-id={image.id} data-selected={isActive} className={styles.file} title="Double-click to restore" onDoubleClick={() => restoreSavedImageHandler(image.id)} onClick={(e) => fileClickHandler(e, image.id)}>
<span className="flex items-center shrink-0"><img src={image.dataUrl} width="35" height="35" draggable={false} style={{ objectFit: "contain", border: "0.1rem solid #888", background: "#fff" }} /></span>
<h4 className="px-0.5">{image.name}</h4>
</button>
);
})}
{appId === "computer" && <h3 className="w-full">Hard Disk Drives</h3>}

</div>
Expand Down
Loading
Loading