- This works differently to MS Paint. Instead of a color for clearing
(and for the inside of shapes) and a color for brushing (and the
outline of shapes), here there's a background and text color for each
cell, collectively treated like the foreground color in MS Paint.
- There's no way to select a foreground color yet other than opening
an image and using the color picker.
Sure, I'm tacking on these properties, but it's better to tack onto
objects than to tack onto strings. I'm not using a type checker yet,
but this is a better situation for type checking. (I could extend Button
with mini classes within ToolsBox and ColorsBox, if need be, to give
clear ownership of these properties.)
I added the filename = os.path.join(...) which invalidated the positive
filename check. I could move the check earlier, but a negative check
should do nicely.
This commit is mostly a dedent, though git may display the diff poorly
due to the shared line window.close()
I've settled on underscores, for now at least.
Generally I use hyphens.
Built-ins actually use hyphens, like `.-dark-mode`.
Maybe I should be using hyphens.
But for now, consistency is what's important, and I'm using underscores.
Now it's really verbose, but I don't have to worry about reusing
the same name twice. Or coming up with new, fun ones.
https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task
> Important: Save a reference to the result of this function, to avoid a task disappearing mid-execution. The event loop only keeps weak references to tasks. A task that isn’t referenced elsewhere may get garbage collected at any time, even before it’s done. For reliable “fire-and-forget” background tasks, gather them in a collection:
>
> background_tasks = set()
>
> for i in range(10):
> task = asyncio.create_task(some_coro(param=i))
>
> # Add task to the set. This creates a strong reference.
> background_tasks.add(task)
>
> # To prevent keeping references to finished tasks forever,
> # make each task remove its own reference from the set after
> # completion:
> task.add_done_callback(background_tasks.discard)
- Move `self.canvas.magnification = self.magnification` up. It happens,
due to internal call_after_refresh, and should happen, before scroll.
- Remove note about other code in JS Paint, I think it's not needed.
- Note my attempts at fixing flicker, which were unsuccessful, so that
I don't try the exact same things later, and feel stupid,
and so I don't accrue so many branches.
Prevent the filename input from being populated in the first place,
instead of resetting after its unintentionally populated when the
directory tree is expanded.
Nip it in the bud. Head it off at the pass. Stop it at the source.
It still needs this new flag to be cleared on a timer, but at least this
timer is near the code for the other timers it needs to trigger after.
Explicitly set filename for Save As dialog, which SHOULD be populated.
I don't want to bother importing Rich's Color class in addition to
Textual's Color class.
This also doesn't work, which might be the fastest if it worked:
style.color.triplet.red = 255 - style.color.triplet.red
style.color.triplet.green = 255 - style.color.triplet.green
style.color.triplet.blue = 255 - style.color.triplet.blue
style.bgcolor.triplet.red = 255 - style.bgcolor.triplet.red
style.bgcolor.triplet.green = 255 - style.bgcolor.triplet.green
style.bgcolor.triplet.blue = 255 - style.bgcolor.triplet.blue
This will make it possible to load localization data for a language
defined by a command-line argument, throughout the program.
Unfortunately this separates the usage of the arguments from their
definitions by a wide margin, making it harder to edit, especially
with AI autocomplete. For now. I could refactor things, either move
the bulk of this file to separate files, or define a configure_app
function up top to use down below.
Giving up on the python translation of the RC file parsing and pre-processing for now, in favor of simply parsing the JS files already generated, which is a much simpler task.
- Confirm discarding changes for Open, New, or Exit, including for
exit via Ctrl+C which was previously handled by a built-in binding.
- Await Save As dialog closing, including when Save triggers Save As.
This is my first time using asynchronous features in Python,
(as far as I remember,) so it's a bit messy.
- Make DialogWindow callback also for Cancel, which means all
DialogWindow usage sites care what button is selected.
- Send RequestClose event for Esc key.
It took a while to figure out this magic.
`textual run --help` says:
> If you are running a file and want to pass command line arguments, wrap the
> filename and arguments in quotes:
>
> textual run "foo.py arg --option"
But it doesn't describe how to handle those arguments.
It's a bit verbose, but for Open, I wanted the _dialog part since "open"
can be both a verb and an adjective, and I didn't like it reading as an
adjective. It doesn't technically disambiguate it, but it reads better.
Verb "[to] open", noun "[dialog to] open", adjective "[part of the dialog to] open"
Adjective "[is] open", noun "[dialog that is] open", adjective "[part of the dialog that is] open"
Anyways this commit just makes it consistent.
Perhaps I could use classes more in place of IDs.
A Textual layout bug is unfortunately making the Yes button HUGE,
and the No button INVISIBLE, until you mouse over the dialog, which is
pretty funny...
"<file> already exists. Do you want to replace it? [Yes]"
These are pretty good candidates, paired together.
For the Free-Form Select icon (⢼⠮), I'm specifically imitating the
asymmetrical star shape from MS Paint.
* Implemented the Fill With Color tool using the algorithm described as
"combined-scan-and-fill span filler" on Wikipedia.
* I added handling for the affected region being None, which turned
out more complicated than I would like...
Some cases may be able to be simplified or removed.
* Also, I moved event.stop() to the top so I don't need to call it in
multiple places when there are multiple return points.
You could imagine having a brush with momentum, that swings around the
mouse without always reaching it. But it's just generally clearer to
not have an inadequate initial region that's then extended.
I mean, let's be honest, most of these symbols are garbage candidates.
But these ones I accidentally left from trying to get ChatGPT to find
emoji for me.
More helpful were https://emojidb.org/ and http://shapecatcher.com/
I wanted to avoid duplicating tool-related state between PaintApp and
Canvas, and prepare for adding different tools with more state and which
will want to live in a separate file.
This makes it slower, when running with `textual run --dev paint.py`;
when running with `python3 paint.py`, it's fine.
When running in dev mode with `textual console` devtool connected, it's
extremely much slower. But if it was faster, you'd have more messages
to scroll through, ha. So it's a tradeoff.*
*Ideally you want it to be fast and for the logs to be compacted.
In the future, I could bypass the message system for performance, but
for now I think it's better to stay idiomatic.
- Remove scrollbar from root
- Fix extra height below color palette due to vertical layout giving even heights
- Add container around canvas to make canvas scrollable