Skip to content

Commit

Permalink
Merge pull request ppy#4506 from bdach/hex-colour-picker
Browse files Browse the repository at this point in the history
Add hex colour picker
  • Loading branch information
peppy authored Jun 15, 2021
2 parents ce4c371 + 7985a47 commit ca3d13f
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 0 deletions.
112 changes: 112 additions & 0 deletions osu.Framework.Tests/Visual/UserInterface/TestSceneHexColourPicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osuTK;
using osuTK.Input;

namespace osu.Framework.Tests.Visual.UserInterface
{
public class TestSceneHexColourPicker : ManualInputManagerTestScene
{
private TestHexColourPicker hexColourPicker;
private SpriteText currentText;
private Box currentPreview;

[SetUpSteps]
public void SetUpSteps()
{
AddStep("create content", () =>
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
hexColourPicker = new TestHexColourPicker(),
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
currentText = new SpriteText(),
new Container
{
Width = 50,
RelativeSizeAxes = Axes.Y,
Child = currentPreview = new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
}
};

hexColourPicker.Current.BindValueChanged(colour =>
{
currentText.Text = $"Current.Value = {colour.NewValue.ToHex()}";
currentPreview.Colour = colour.NewValue;
}, true);
});
}

[Test]
public void TestExternalChange()
{
Colour4 colour = Colour4.Yellow;

AddStep("set current colour", () => hexColourPicker.Current.Value = colour);

AddAssert("hex code updated", () => hexColourPicker.HexCodeTextBox.Text == colour.ToHex());
assertPreviewUpdated(colour);
}

[Test]
public void TestTextBoxBehaviour()
{
clickTextBox();
AddStep("insert valid colour", () => hexColourPicker.HexCodeTextBox.Text = "#ff00ff");
assertPreviewUpdated(Colour4.Magenta);
AddAssert("current not changed yet", () => hexColourPicker.Current.Value == Colour4.White);

AddStep("commit text", () => InputManager.Key(Key.Enter));
AddAssert("current updated", () => hexColourPicker.Current.Value == Colour4.Magenta);

clickTextBox();
AddStep("insert invalid colour", () => hexColourPicker.HexCodeTextBox.Text = "c0d0");
AddStep("commit text", () => InputManager.Key(Key.Enter));
AddAssert("current not changed", () => hexColourPicker.Current.Value == Colour4.Magenta);
AddAssert("old hex code restored", () => hexColourPicker.HexCodeTextBox.Text == "#FF00FF");
}

private void clickTextBox()
=> AddStep("click text box", () =>
{
InputManager.MoveMouseTo(hexColourPicker.HexCodeTextBox);
InputManager.Click(MouseButton.Left);
});

private void assertPreviewUpdated(Colour4 expected)
=> AddAssert("preview colour updated", () => hexColourPicker.Preview.Current.Value == expected);

private class TestHexColourPicker : BasicHexColourPicker
{
public TextBox HexCodeTextBox => this.ChildrenOfType<TextBox>().Single();
public ColourPreview Preview => this.ChildrenOfType<ColourPreview>().Single();
}
}
}
50 changes: 50 additions & 0 deletions osu.Framework/Graphics/UserInterface/BasicHexColourPicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Graphics.Shapes;

namespace osu.Framework.Graphics.UserInterface
{
public class BasicHexColourPicker : HexColourPicker
{
public BasicHexColourPicker()
{
Background.Colour = FrameworkColour.GreenDarker;

Padding = new MarginPadding(20);
Spacing = 10;
}

protected override TextBox CreateHexCodeTextBox() => new BasicTextBox
{
Height = 40
};

protected override ColourPreview CreateColourPreview() => new BasicColourPreview();

private class BasicColourPreview : ColourPreview
{
private readonly Box previewBox;

public BasicColourPreview()
{
InternalChild = previewBox = new Box
{
RelativeSizeAxes = Axes.Both
};
}

protected override void LoadComplete()
{
base.LoadComplete();

Current.BindValueChanged(_ => updatePreview(), true);
}

private void updatePreview()
{
previewBox.Colour = Current.Value;
}
}
}
}
145 changes: 145 additions & 0 deletions osu.Framework/Graphics/UserInterface/HexColourPicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;

namespace osu.Framework.Graphics.UserInterface
{
public abstract class HexColourPicker : CompositeDrawable, IHasCurrentValue<Colour4>
{
private readonly BindableWithCurrent<Colour4> current = new BindableWithCurrent<Colour4>();

public Bindable<Colour4> Current
{
get => current.Current;
set => current.Current = value;
}

public new MarginPadding Padding
{
get => content.Padding;
set => content.Padding = value;
}

/// <summary>
/// Sets the spacing between the hex input text box and the colour preview.
/// </summary>
public float Spacing
{
get => spacer.Width;
set => spacer.Width = value;
}

/// <summary>
/// The background of the control.
/// </summary>
protected readonly Box Background;

private readonly Container content;

private readonly TextBox hexCodeTextBox;
private readonly Drawable spacer;
private readonly ColourPreview colourPreview;

protected HexColourPicker()
{
Current.Value = Colour4.White;

Width = 300;
AutoSizeAxes = Axes.Y;

InternalChildren = new Drawable[]
{
Background = new Box
{
RelativeSizeAxes = Axes.Both
},
content = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension()
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new[]
{
hexCodeTextBox = CreateHexCodeTextBox().With(d =>
{
d.RelativeSizeAxes = Axes.X;
d.CommitOnFocusLost = true;
}),
spacer = Empty(),
colourPreview = CreateColourPreview().With(d => d.RelativeSizeAxes = Axes.Both)
}
}
}
}
};
}

/// <summary>
/// Creates the text box to be used for specifying the hex code of the target colour.
/// </summary>
protected abstract TextBox CreateHexCodeTextBox();

/// <summary>
/// Creates the control that will be used for displaying the preview of the target colour.
/// </summary>
protected abstract ColourPreview CreateColourPreview();

protected override void LoadComplete()
{
base.LoadComplete();

Current.BindValueChanged(_ => updateState(), true);

hexCodeTextBox.Current.BindValueChanged(_ => tryPreviewColour());
hexCodeTextBox.OnCommit += commitColour;
}

private void updateState()
{
hexCodeTextBox.Text = Current.Value.ToHex();
colourPreview.Current.Value = Current.Value;
}

private void tryPreviewColour()
{
if (!Colour4.TryParseHex(hexCodeTextBox.Text, out var colour) || colour.A < 1)
return;

colourPreview.Current.Value = colour;
}

private void commitColour(TextBox sender, bool newText)
{
if (!Colour4.TryParseHex(sender.Text, out var colour) || colour.A < 1)
{
Current.TriggerChange(); // restore previous value.
return;
}

Current.Value = colour;
}

public abstract class ColourPreview : CompositeDrawable
{
public Bindable<Colour4> Current = new Bindable<Colour4>();
}
}
}

0 comments on commit ca3d13f

Please sign in to comment.