DesModder icon indicating copy to clipboard operation
DesModder copied to clipboard

Shape Generator Plugin

Open lafkpages opened this issue 1 year ago • 9 comments

Adds a simple utility plugin that helps quickly generate shapes and their equations in Desmos. It currently only supports rotatable ellipses and rectangles, but I'll be adding more soon.

The idea for this plugin came to me while trying to do some art in Desmos. I needed to add some shapes on top of an image I had for reference. This plugin would save me from having to write the equations for different shapes out by hand, substitute variables to more easily tweak them, and then substitute in the actual values once I'm done.

lafkpages avatar Jul 20 '24 01:07 lafkpages

This is cool! Reminds me of Desmos Lock, but this has a much better UX.

It's such a seamless process of integration with Desmos. The "OK" button has such a nice placement. The insertion of extra sliders and the main expresion (and their cleanup) works well, though I haven't tested it thoroughly.

Without looking too deeply at the code, I see this PR as consisting of three parts:

  1. Adding buttons on the "Add item" menu to insert several pre-populated expressions at once
    • To support part 2, this marks one expression as "OK" and the rest as deleteable.
  2. Adding an "OK" button to certain expressions that, when pressed:
    • replaces slider variables with their definitions in the "OK" expression
    • deletes the marked deleteable dependencies
    • removes the "OK" button
  3. Simplifying the "OK" expression (hopefully this is all that CortexJS is used for)

I'd be interested in merging this without part 3 (and likely merging it separately afterwards). In my experience, a big PR that does several separate things ends up neglecting some part of some of them.

I'd rather focus on part 1 and 2 for this PR, taking care to deal with edge cases like hidden folders and variables with duplicate names (defined as functions, sliders, tables, regressions, etc). Then we can later focus on part 3, taking care to deal with all the involved edge cases.

I'm also interested (as a separate PR) of allowing users to define their own "shapes" (which only affects part 1). A custom shape could be stored as a graph like https://www.desmos.com/calculator/dyymzrjd8i. This is pretty similar to https://github.com/DesModder/DesModder/pull/748, so I'll see what I could do to polish that up into a working version (unless @radian628 wants to wrap it up) so we could re-use a bunch of the machinery.

jared-hughes avatar Jul 20 '24 11:07 jared-hughes

I'd be interested in merging this without part 3 (and likely merging it separately afterwards). In my experience, a big PR that does several separate things ends up neglecting some part of some of them.

Alright, I've removed all CortexJS logic for now.

I'm also interested (as a separate PR) of allowing users to define their own "shapes" (which only affects part 1). A custom shape could be stored as a graph like https://www.desmos.com/calculator/dyymzrjd8i.

Yeah that could be interesting.

lafkpages avatar Aug 18 '24 17:08 lafkpages

The display: none !important; is problematic when the expressions list can be scrolled. Desmos does measurements and calculations on the vertical height to avoid showing items that are off-screen. The display: none breaks that.

My first thought for a fix was marking the secret helpers as secret: true, which hides them in the expressions list (while hidden: true hides them on the graphpaper). But there's a slight problem that anyone with ?authorFeatures enabled (such as through the Calculator Settings plugin) would be able to see the secret helpers, and it would clutter their vertical space.

A simple approach would be to use a collapsed secret folder (at the end of the expressions list) to hide all the expressions that you don't want to show to the user. That way, it's hidden from most users, and those with ?authorFeatures would only have a slight vertical affliction. (This may also help cleaning up -- the OK button could just delete the folder in a method analogous to hitting the "X").

A complicated approach would be using some other approach to hide the expressions. One way would be to set their item models to have filteredBySearch = true, such as by Calc.controller.getItemModelByIndex(0).filteredBySearch = true. That would break on a Desmos Ctrl+F search (which mutates the filteredBySearch booleans), but I imagine there's some workaround there.

jared-hughes avatar Aug 20 '24 11:08 jared-hughes

My first thought for a fix was marking the secret helpers as secret: true, which hides them in the expressions list

I thought I tried that but I could only get folders to be hidden and not individual expressions?

But there's a slight problem that anyone with ?authorFeatures enabled (such as through the Calculator Settings plugin) would be able to see the secret helpers, and it would clutter their vertical space.

True

A simple approach would be to use a collapsed secret folder (at the end of the expressions list) to hide all the expressions that you don't want to show to the user.

Yeah, I tried that before. Only problem is that the only way to add my expressions into a folder is by using Calc.setState(). Specifying folderId in setExpression does quite literally nothing. But having to getState(), filter, update, and setState() is painful.

lafkpages avatar Aug 20 '24 11:08 lafkpages

I pushed a commit to remove the disabling of the "save" button because there's at least 3 other ways to save a graph.

A few final bugs:

  1. On an empty graph with ?showIDs, the expression list HTML is more blank, and the plugin does not load properly (Uncaught Error: Could not find the expression list at _ShapeGenerator.afterEnable (index.ts:232:13))
  2. Clearing the graph (cog menu > Delete All) or opening a new graph from the sidebar (which does a Calc.getState(...)) does not reset isEditingShape, so the "ellipse" and "rectangle" buttons remain grayed out and can't be used.
  3. New expressions always get added at the end, instead of after the selected item. (Don't worry about fixing this one. I can do it later).

jared-hughes avatar Aug 21 '24 09:08 jared-hughes

  1. On an empty graph with ?showIDs, the expression list HTML is more blank, and the plugin does not load properly (Uncaught Error: Could not find the expression list at _ShapeGenerator.afterEnable (index.ts:232:13))

~Oops, that should be an easy fix.~ Edit: fixed.

2. Clearing the graph (cog menu > Delete All) or opening a new graph from the sidebar (which does a Calc.getState(...)) does not reset isEditingShape, so the "ellipse" and "rectangle" buttons remain grayed out and can't be used.

Is there a dispatch for clearing, resetting, or opening a new graph that I can hook into?

3. New expressions always get added at the end, instead of after the selected item. (Don't worry about fixing this one. I can do it later).

Alright

lafkpages avatar Aug 21 '24 09:08 lafkpages

@jared-hughes are you still going to fix this? Or should I? And what about the dispatches?

lafkpages avatar Aug 28 '24 18:08 lafkpages

Is there a dispatch for clearing, resetting, or opening a new graph that I can hook into?

@lafkpages Clearing, resetting, and opening a new graph always dispatch the set-state event which comes with the new state: GraphState as payload. The state is undefined when clearing and has the new graph state otherwise.

You can find what these dispatchers are by calling

Calc.controller.dispatcher.register(e => console.log(e));

on an empty graph. This and a lot more useful info on dispatchers is available in dispatch-handlers.md

SlimRunner avatar Sep 20 '24 03:09 SlimRunner

@lafkpages I also looked into the dispatchers. Using DesThree and some debugging in combination with DesModder dispatch guide I came up with the following:

(function() {
  'use strict';
  const expr = Calc.controller.createItemModel({
    latex: 'x^3',
    type: 'expression',
    id: Calc.controller.generateId(),
    color: Calc.controller.getNextColor()
  });
  Calc.controller._toplevelNewItemAtSelection(expr, {shouldFocus: true});
  Calc.controller._closeAddExpression();
  Calc.controller.dispatch({});
}());

Obviously this is no good for this repo, so I looked further and implemented a dispatcher using DesModder tooling that adds a random polynomial at the selected expression when you call Calc.controller.dispatch({type:"dsm-simple-plugin-add-random-polynomial"}). This code is in my personal fork for a sample plugin. Feel free to inspect the diff if you want to adapt it for your plugin.

SlimRunner avatar Sep 20 '24 05:09 SlimRunner