charlesroddie / MathAtom

Structural representations of visual mathematics expressions for use in .Net rendering libraries
MIT License
7 stars 0 forks source link

Atom additions for editing #5

Open charlesroddie opened 6 years ago

charlesroddie commented 6 years ago

We could put extra cases inside the Atom type:

type Atom =
    ...
    /// Represents a blank, displayed as a square, where input is needed.
    | InputSquare
    // An Atom may have at most one Selected or Cursor inside.
    | Selected of Atom 
    | Cursor

type Input =
    | Del | Backspace
    | Left | Right | Up | Down
    | AddAtom of Atom
    | AddChar of char

let process (a:Atom) (i:Input) : Atom = ...

Alternatively we could make separate Atom from cursor position / selection:

type Atom =
    ...
    /// Represents a blank, displayed as a square, where input is needed.
    | InputSquare

type EditingState =
    // This requires identifying Atoms, so that atoms need to be distinct as objects:
    | Selected of Atom 
    | Cursorposition of ?

@Happypig375 thoughts appreciated as you work on the MathEditor port.

I think the first approach will be much cleaner. In the simplest implementation it may not be the most performant as l/r/u/d moves would lead to laying out everything again, but as long as it takes << 1/60 s to process a change we don't need to worry.

ForNeVeR commented 6 years ago

I was thinking about an alternate route we could discuss: maybe we could store the cursor position in the original text, and Atom could have some way of mapping between original text and its' internal contents?

Consider that:

\frac{2+2}{3+|3} (where | denotes a cursor)

Our hypothetical FracAtom could store source info (infamous SourceSpans from WPF-Math or something else) and provide a mapping: 3+3 is mapped to RowAtom that consists of Char(3), Symbol(+) and Char(3). That way, our Atoms shouldn't store any selection/cursor information, but the editor (even the editor that doesn't allow the user to edit LaTeX markup) could always properly calculate the position.

Also this would solve the navigation problem: having a complex Atom, it's usually nontrivial to calculate what should happen if the user press button on the keyboard. But with text-based representation, it will be easier I think.

ForNeVeR commented 6 years ago

Having said all of this, I don't think that initial idea from the OP is bad.

But I'm not sure if we want to add all the editor stuff to this library in such a way. Consider that every math rendering library will now need to support this InputSquare. And what if math editor library will need some additional atoms aside from that?

I'd recommend to add an extendable mechanism that will be useful for everyone, no matter if they're editing math or rendering it or whatever. Probably let's add some "tags" to our atoms? E.g.

type TaggedAtom<'a> = { atom : Atom; tag : 'a }

Allow the user to add their own "tags" to atoms and add extension points to the renderer, so the user code is able to analyze the tags and instruct the renderer accordingly. Something like this:

type MyAtomState = Rendered | InputSquare | Selected
type MyMathEditorAtom = TaggedAtom<MyState>
let myRenderer =
  let baseRenderer = MathAtom.createRenderer<MyAtomState>()
  { baseRenderer with
    getBackground = fun (a : TaggedAtom) -> match a.tag with
      | Rendered -> baseRenderer.getBackground a
      | InputSquare -> Color.Red
      | Selected -> Color.Blue }

(that's just a quick sample, I'm not sure what actual extension points would be)

charlesroddie commented 6 years ago

@ForNeVeR maybe we could store the cursor position in the original text, and Atom could have some way of mapping between original text and its' internal contents?

having a complex Atom, it's usually nontrivial to calculate what should happen if the user press → button on the keyboard. But with text-based representation, it will be easier I think.

I don't think we should retain LaTeX because:

  1. It should be easier to work with the parsed expression than the original text. If it ends up harder than we will have done a bad job with the Atom type. Left/Right may be easy in LaTeX if it corresponds to moving left/right in the string, but it should not be hard with the Atom, and other operations (Up/Down/Delete/Insert) should be much easier to work with in the Atom.
  2. This would tie the library to LaTeX. @Happypig375 wants to support more input languages, such as MathML. I think the Atom should be guided by LaTeX and support LaTeX as an input and output format, but not be LaTeX-specific.
charlesroddie commented 6 years ago

Consider that every math rendering library will now need to support this InputSquare. And what if math editor library will need some additional atoms aside from that?

We are supporting 2 rendering libraries, which may become 1 in future.

Options for differing feature support between wpf-math and csharpmath:

  1. Support the union of features, and consuming libraries can ignore cases that are not supported (with an explicit match and error). We could if needed package up CSharpMath-only features in one place so this is easier and the same for wpf-math.

  2. Support the intersection of features and add an extensibility solution. But this needs to add new Atom cases and add the processing for new atoms in various places, so it will be complex to use and manage. We'd need to spec it out and then make sure there is one person in each rendering project who is able to use it, and that ordinary contributors can code without understanding this mechanism.

  3. Do the extensions or subtractions in forks of MathAtom.

Happypig375 commented 6 years ago

@charlesroddie Support 3. The main work of locating an atom in MathEditor is MathListIndex. (Correct the syntax if necessary)

type MathListIndex =
  member val AtomIndex = 0 with get, set
  member val SubIndex : MathListIndex option with get, set
  member val SubIndexType : MathListSubIndexType with get, set

type MathListSubIndexType =
    | None
    /// The position in the subindex is an index into the nucleus
    | Nucleus
    /// The subindex indexes into the superscript.
    | Superscript
    /// The subindex indexes into the subscript
    | Subscript
    /// The subindex indexes into the numerator (only valid for fractions)
    | Numerator
    /// The subindex indexes into the denominator (only valid for fractions)
    | Denominator
    /// The subindex indexes into the radicand (only valid for radicals)
    | Radicand
    /// The subindex indexes into the degree (only valid for radicals)
    | Degree