MVCoconut / coconut.vdom

Coconut rendering through virtual-dom.
The Unlicense
19 stars 8 forks source link

Add float support #51

Open tkwiatek opened 1 month ago

tkwiatek commented 1 month ago

Argument for Enabling Floats in the UI Framework:

The decision to not render Float values directly in the hacksaw.ui framework is understandable given concerns about localization and formatting. However, in practical applications, especially those involving dynamic or interactive elements like sliders and audio controls, the ability to render and manipulate Float values directly can greatly enhance the developer experience and the functionality of the UI. Below is an example of a PanVolumeView component that illustrates this point:

class PanVolumeView extends View {
    @:state var pan:Float = 0.0;
    @:state var volume:Float = 0.75;

    function volumeChange(value:Float) {
        trace('volumeChange', value);
        volume = value;
    }

    function panChange(value:Float) {
        trace('panChange', value);
        pan = value;
    }

    function formatVolume(value:Float) {
        return Math.round(value * 100);
    }

    function formatPan(value:Float) {
        final maxDB = 120;
        final rounded = Math.abs(Math.round(value * maxDB));
        if (value < 0) {
            return 'L=$rounded';
        } else if (value > 0) {
            return 'R=$rounded';
        } else {
            return 'C=0';
        }
    }

    function render()
        <div>
            <div>
                <span>Pan: ${formatPan(pan)}dB</span>
                <RangeInput min={-1.0} max={1.0} value={pan} step={0.001} onchange={panChange}/>
            </div>
            <div>
                <span>Volume: ${formatVolume(volume)}%</span>
                <RangeInput min={0.0} max={1.0} value={volume} step={0.001} onchange={volumeChange}/>
            </div>
        </div>;
}

class RangeInput extends View {
    @:attr var min:Null<Float> = null;
    @:attr var max:Null<Float> = null;
    @:attr var step:Null<Float> = null;
    @:attr var value:Null<Float> = null;

    @:optional @:attr function onchange(value:Float);

    function onInput(v) {
        if (onchange != null) {
            onchange(Std.parseFloat(v));
        }
    }

    function render()
        <div>
            <input type="range" min='${min}' max='${max}' value='${value}' step='${step}' oninput={onInput(event.src.value)} />
        </div>;
}

Explanation:

  1. Enhanced Real-Time User Interactions:

    • In applications with real-time user interactions, such as audio controls (e.g., volume and pan sliders), precise control is crucial. The RangeInput component in the example allows users to adjust pan and volume with fine granularity using Float values.
    • A step size of 0.001 ensures a smooth and precise user experience. Without direct Float support, achieving this level of granularity would be cumbersome and might necessitate additional conversion logic, potentially leading to performance overhead or added complexity.
  2. Maintaining Format Control:

    • Although floats are used directly in the logic, the display of these values remains fully under the developer's control. Functions like formatVolume and formatPan convert the float values into user-friendly strings for display.
    • This approach balances the need for precision with the ability to format output appropriately for the user interface.
  3. Reducing Boilerplate Code:

    • Allowing direct rendering of Float values would reduce the need for repetitive conversion code across components. Developers could bind Float values directly to UI elements, simplifying the codebase and minimizing the likelihood of errors in manual conversion logic.
  4. Supporting Advanced Use Cases:

    • In more complex scenarios—such as visualizing scientific data, performing financial calculations, or any application where precision is key—direct rendering of Float values would significantly enhance the framework's flexibility and power.
    • For instance, in an application where users adjust scientific measurements, rendering Float values directly allows them to see exact values, avoiding oversimplification or misinterpretation from forced string conversion.
  5. Internationalization Applies to Integers Too:

    • The concern about formatting and localization is not limited to floats—integers also need to be properly internationalized. For example, different regions use different separators for thousands (e.g., 1,000 vs. 1.000), and these need to be formatted appropriately. However, this doesn’t mean that integers shouldn’t be allowed to render directly; instead, developers should have control over formatting, whether working with floats or integers.
  6. Using Int Instead of Float Adds Extra Code and Complexity:

    • While it is technically possible to use Int values and scale them to represent floats (e.g., multiplying by 1000 to avoid using Float directly), this approach adds unnecessary complexity to the codebase. Developers must write additional logic to scale, convert, and then correctly format these values, which increases the likelihood of errors.
    • For example, to represent 0.001 using an integer, you would need to store it as 1 and manually convert it back to 0.001 when needed. This not only complicates the code but also makes it harder to maintain and understand.
  7. Avoiding Unnecessary Developer Work:

    • Developers are currently required to write conversion logic even in simple cases where default float rendering would suffice. This requirement can increase development time and complexity, particularly for those new to the framework.
  8. Avoiding Redundancy in Simple Applications:

    • In applications where precise formatting isn't a concern, the need to convert Float to String can feel redundant and unnecessary.
  9. Minimizing Errors in Manual Conversion:

    • While the framework aims to prevent errors, it also introduces the possibility of mistakes in manually written conversion logic. If developers don’t handle the conversion correctly, it could result in inconsistent or incorrect displays.