~raph/interp-toy

1fc3ed62e5e401d69cdedae624ebcd28318f074a — Raph Levien 2 years ago e811bfe
Add closure-based lens abstraction with pairing
4 files changed, 188 insertions(+), 4 deletions(-)

M Cargo.lock
M Cargo.toml
A src/lens2.rs
M src/main.rs
M Cargo.lock => Cargo.lock +3 -3
@@ 317,7 317,7 @@ version = "0.1.0"
dependencies = [
 "druid 0.3.0 (git+https://github.com/xi-editor/druid?rev=6fc687e39f01a4f0aeda8cbbd919ccd28a9e9f34)",
 "nalgebra 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "rbf-interp 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "rbf-interp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]


@@ 746,7 746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "rbf-interp"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "nalgebra 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",


@@ 1110,7 1110,7 @@ dependencies = [
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
"checksum rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019"
"checksum rbf-interp 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bdf09df57773dac232830bff95492243e0ecfdd4333bda034f540b92b7c4ad34"
"checksum rbf-interp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79c48584d4fc9898e2983c72bd6e418c5ea33ea99588b8d156e2a6be45ed4ffe"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum rental 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "01916ebd9fc2e81978a5dc9542a2fa47f5bb2ca3402e14c7cc42d6e3c5123e1f"

M Cargo.toml => Cargo.toml +1 -1
@@ 8,5 8,5 @@ edition = "2018"

[dependencies]
druid = {git = "https://github.com/xi-editor/druid", rev = "6fc687e39f01a4f0aeda8cbbd919ccd28a9e9f34" }
rbf-interp = "0.1.2"
rbf-interp = "0.1.3"
nalgebra = "0.18"

A src/lens2.rs => src/lens2.rs +183 -0
@@ 0,0 1,183 @@
// Copyright 2019 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Support for lenses, a way of focusing on subfields of data. This is a variant
//! on the one in druid, which uses a closure for both get and set.

use std::marker::PhantomData;

use druid::kurbo::Size;

use druid::{
    Action, BaseState, BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, UpdateCtx,
    Widget,
};

/// A lens is a datatype that gives access to a part of a larger
/// data structure.
///
/// A simple example of a lens is a field of a struct; in this case,
/// the lens itself is zero-sized. Another case is accessing an array
/// element, in which case the lens contains the array index.
///
/// Many `Lens` implementations will be derived by macro, but custom
/// implementations are practical as well.
///
/// The name "lens" is inspired by the [Haskell lens] package, which
/// has generally similar goals. It's likely we'll develop more
/// sophistication, for example combinators to combine lenses.
///
/// [Haskell lens]: http://hackage.haskell.org/package/lens
pub trait Lens2<T, U> {
    /// Get non-mut access to the field.
    ///
    /// Consider renaming, the signature suggests returning a reference, but
    /// it actually calls a closure with the field, allowing more flexibility
    /// in synthesizing it on demand.
    fn get<V, F: FnOnce(&U) -> V>(&self, data: &T, f: F) -> V;

    /// Get mutable access to the field.
    ///
    /// This method is defined in terms of a closure, rather than simply
    /// yielding a mutable reference, because it is intended to be used
    /// with value-type data (also known as immutable data structures).
    /// For example, a lens for an immutable list might be implemented by
    /// cloning the list, giving the closure mutable access to the clone,
    /// then updating the reference after the closure returns.
    fn with_mut<V, F: FnOnce(&mut U) -> V>(&self, data: &mut T, f: F) -> V;
}

pub struct Pair<L1, L2> {
    lens1: L1,
    lens2: L2,
}

impl<T: Data, U1: Data, U2: Data, L1: Lens2<T, U1>, L2: Lens2<T, U2>> Lens2<T, (U1, U2)> for Pair<L1, L2> {

    fn get<V, F: FnOnce(&(U1, U2)) -> V>(&self, data: &T, f: F) -> V {
        self.lens1.get(data, |data1|
            self.lens2.get(data, |data2| {
                let data = (data1.to_owned(), data2.to_owned());
                f(&data)
            }))
    }

    fn with_mut<V, F: FnOnce(&mut (U1, U2)) -> V>(&self, data: &mut T, f: F) -> V {
        let ((data1, data2), val, delta1, delta2) = self.lens1.get(data, |data1|
            self.lens2.get(data, |data2| {
                let mut data = (data1.to_owned(), data2.to_owned());
                let val = f(&mut data);
                let delta1 = data1.same(&data.0);
                let delta2 = data2.same(&data.1);
                (data, val, delta1, delta2)
            }));
        if delta1 {
            self.lens1.with_mut(data, |d1| *d1 = data1);
        }
        if delta2 {
            self.lens2.with_mut(data, |d2| *d2 = data2);
        }
        val
    }
}

// A case can be made this should be in the `widget` module.

/// A wrapper for its widget subtree to have access to a part
/// of its parent's data.
///
/// Every widget in druid is instantiated with access to data of some
/// type; the root widget has access to the entire application data.
/// Often, a part of the widget hiearchy is only concerned with a part
/// of that data. The `Lens2Wrap` widget is a way to "focus" the data
/// reference down, for the subtree. One advantage is performance;
/// data changes that don't intersect the scope of the lens aren't
/// propagated.
///
/// Another advantage is generality and reuse. If a widget (or tree of
/// widgets) is designed to work with some chunk of data, then with a
/// lens that same code can easily be reused across all occurrences of
/// that chunk within the application state.
///
/// This wrapper takes a [`Lens2`] as an argument, which is a specification
/// of a struct field, or some other way of narrowing the scope.
///
/// [`Lens2`]: trait.Lens.html
pub struct Lens2Wrap<U, L, W> {
    inner: W,
    lens: L,
    // The following is a workaround for otherwise getting E0207.
    phantom: PhantomData<U>,
}

impl<U, L, W> Lens2Wrap<U, L, W> {
    /// Wrap a widget with a lens.
    ///
    /// When the lens has type `Lens2<T, U>`, the inner widget has data
    /// of type `U`, and the wrapped widget has data of type `T`.
    pub fn new(inner: W, lens: L) -> Lens2Wrap<U, L, W> {
        Lens2Wrap {
            inner,
            lens,
            phantom: Default::default(),
        }
    }
}

impl<T, U, L, W> Widget<T> for Lens2Wrap<U, L, W>
where
    T: Data,
    U: Data,
    L: Lens2<T, U>,
    W: Widget<U>,
{
    fn paint(&mut self, paint_ctx: &mut PaintCtx, base_state: &BaseState, data: &T, env: &Env) {
        let inner = &mut self.inner;
        self.lens
            .get(data, |data| inner.paint(paint_ctx, base_state, data, env));
    }

    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
        let inner = &mut self.inner;
        self.lens.get(data, |data| inner.layout(ctx, bc, data, env))
    }

    fn event(
        &mut self,
        event: &Event,
        ctx: &mut EventCtx,
        data: &mut T,
        env: &Env,
    ) -> Option<Action> {
        let inner = &mut self.inner;
        self.lens
            .with_mut(data, |data| inner.event(event, ctx, data, env))
    }

    fn update(&mut self, ctx: &mut UpdateCtx, old_data: Option<&T>, data: &T, env: &Env) {
        let inner = &mut self.inner;
        let lens = &self.lens;
        if let Some(old_data) = old_data {
            lens.get(old_data, |old_data| {
                lens.get(data, |data| {
                    if !old_data.same(data) {
                        inner.update(ctx, Some(old_data), data, env);
                    }
                })
            })
        } else {
            lens.get(data, |data| inner.update(ctx, None, data, env));
        }
    }
}

M src/main.rs => src/main.rs +1 -0
@@ 4,6 4,7 @@ use druid::{LensWrap, UiMain, UiState, Widget};

mod app_state;
mod interp_pane;
mod lens2;
mod list;

use app_state::{lenses, AppState, InterpPt, Master};