Thought Cloud UI #6: Change your mind

The Thought Cloud demo is almost feature complete. Just a few more changes.

The application is not very interactive yet. The user should be able to switch focus from one thought to another. They should also be able to edit thought text. Let’s take these features one at a time.

When you think about a user switching their focus from one object to another, what word comes to mind? If you said “navigation”, then we are on the same page. As the user switches focus from one thought to another, they are navigating through the cloud. They aren’t changing the cloud itself. They are only changing their own point-of-view. We need a place to store that point-of-view.

public class CloudNavigationModel
{
    private readonly Cloud _cloud;

    private Independent<Thought> _focusThought = new Independent<Thought>();

    public CloudNavigationModel(Cloud cloud)
    {
        _cloud = cloud;
    }

    public Thought FocusThought
    {
        get { return _focusThought.Value ?? _cloud.CentralThought.Value; }
        set { _focusThought.Value = value; }
    }
}

The focus thought is independent. The user can change it whenever they want. If they haven’t set it yet, then it defaults to the central thought of the cloud. We don’t just initialize the focus thought to the central thought, because this is another example of honoring the default value. The default of null is a valid value, and should indicate that we want to focus on the cloud’s central thought. As an added bonus, if the cloud’s central thought changes after the navigation model is initialized, the focus thought will shift as well.

Like any navigation model, the cloud navigation model should be injected into the view model. We’ll do that in the MainViewModel, where the cloud view models are constructed.

public IEnumerable<CloudTabViewModel> OpenClouds
{
    get
    {
        return
            from cloud in _navigationModel.OpenClouds
            select new CloudTabViewModel(cloud, new CloudNavigationModel(cloud));
    }
}

Now each cloud view model has its own navigation model. We can easily switch all of the positioning code from the central thought to the focus thought. But how do we change the focus thought? The user should switch focus when they click on a thought. So we add a command to the ThoughtViewModel.

public class ThoughtViewModelActual : ThoughtViewModel
{
    ...

    public ICommand Focus
    {
        get
        {
            return MakeCommand
                .Do(() =>
                {
                    if (_container.FocusThought != _thought)
                        _container.FocusThought = _thought;
                });
        }
    }
}

Then we can just use InvokeDataCommand to execute this command on click. When the user clicks on a thought bubble, the focus thought is changed. This causes the positioning to go out-of-date, and all of the thought bubbles to shuffle.

Edit your thoughts

The user needs to change the text of a thought in two circumstances. First, when they have just added the thought. And second, when they click on it. Since clicking on a thought already makes it the focus, we’ll say that clicking on the focus thought puts it in edit mode.

Only one thought at a time can be in edit mode, so we’ll store that edit thought in the navigation model.

public class CloudNavigationModel
{
    ...

    private Independent<Thought> _editThought = new Independent<Thought>();

    public Thought EditThought
    {
        get { return _editThought; }
        set { _editThought.Value = value; }
    }
}

The view model needs a property that will determine when the text box is visible. It will be true when this thought is the edit thought.

public class ThoughtViewModelActual : ThoughtViewModel
{
    ...

    public bool Editing
    {
        get { return _container.EditThought == _thought; }
    }
}

When the user clicks the thought bubble, it enters edit mode if it is already the focus thought. So let’s add that to the Focus command.

public class ThoughtViewModelActual : ThoughtViewModel
{
    ...

    public ICommand Focus
    {
        get
        {
            return MakeCommand
                .Do(() =>
                {
                    if (_container.FocusThought != _thought)
                        _container.FocusThought = _thought;
                    else if (_container.EditThought != _thought)
                        _container.EditThought = _thought;
                    else
                        _container.EditThought = null;
                });
        }
    }
}

When the user clicks on the background, we want to exit edit mode. So we bind a command to the click event of the background border.

public class CloudViewModel : IThoughtContainer
{
    ...

    public ICommand ClearEdit
    {
        get
        {
            return MakeCommand
                .Do(() => _navigation.EditThought = null);
        }
    }
}

Finally, when the user adds a new thought, we want to put that thought into edit mode. So we add that to the NewThought command.

public class CloudViewModel : IThoughtContainer
{
    ...

    public ICommand NewThought
    {
        get
        {
            return MakeCommand.Do(() =>
            {
                Thought thought = _cloud.NewThought();
                Thought focusThought = _navigation.FocusThought;
                if (focusThought == null)
                {
                    focusThought = _cloud.NewThought();
                    _cloud.CentralThought = focusThought;
                }
                focusThought.LinkTo(thought);
                _navigation.EditThought = thought;
            });
        }
    }
}

With the Navigation Model pattern, we can keep information about the user’s point-of-view. Several different view models can interact with and respond to this navigation model. They don’t need to pass messages to each other. It’s a simple way to help the user browse and edit their information.

Leave a Reply

You must be logged in to post a comment.