/// <summary> /// Handle selecting the row when clicked /// </summary> /// <param name="row">UI row that was clicked on</param> private void OnClick(DifficultyRow row) { if (!diffs.ContainsKey(row.Name)) { return; } DeselectDiff(); // Select a difficulty selected = row; if (!loading) { selectedMemory[currentCharacteristic.beatmapCharacteristicName] = selected.Name; } var selImage = selected.Background; selImage.color = new Color(selImage.color.r, selImage.color.g, selImage.color.b, 1.0f); var diff = diffs[row.Name]; BeatSaberSongContainer.Instance.difficultyData = diff.DifficultyBeatmap; njsField.text = diff.NoteJumpMovementSpeed.ToString(); songBeatOffsetField.text = diff.NoteJumpStartBeatOffset.ToString(); envRemoval.UpdateFromDiff(diff.envRemoval); }
/// <summary> /// Load song data and set up listeners on UI elements /// </summary> public void Start() { if (Song?.difficultyBeatmapSets != null) { Characteristics = Song.difficultyBeatmapSets.GroupBy(it => it.beatmapCharacteristicName).ToDictionary( characteristic => characteristic.Key, characteristic => characteristic.SelectMany(i => i.difficultyBeatmaps).GroupBy(map => map.difficulty).ToDictionary( grouped => grouped.Key, grouped => new DifficultySettings(grouped.First()) ), StringComparer.OrdinalIgnoreCase ); } else { Characteristics = new Dictionary <string, Dictionary <string, DifficultySettings> >(); } foreach (Transform child in transform) { // Add event listeners, it's hard to do this staticly as the rows are prefabs // so they can't access the parent object with this script var row = new DifficultyRow(child); rows.Add(row); row.Toggle.onValueChanged.AddListener((val) => OnChange(row, val)); row.Button.onClick.AddListener(() => OnClick(row)); row.NameInput.onValueChanged.AddListener((name) => OnValueChanged(row, name)); row.Copy.onClick.AddListener(() => SetCopySource(row)); row.Save.onClick.AddListener(() => SaveDiff(row)); row.Revert.onClick.AddListener(() => Revertdiff(row)); row.Paste.onClick.AddListener(() => DoPaste(row)); } }
/// <summary> /// Save the diff /// </summary> /// <param name="row">UI row that was clicked on</param> private void SaveDiff(DifficultyRow row) { var localDiff = diffs[row.Name]; var firstSave = localDiff.ForceDirty; localDiff.Commit(); row.ShowDirtyObjects(false, true); var Song = BeatSaberSongContainer.Instance.song; var diff = localDiff.DifficultyBeatmap; if (!Song.difficultyBeatmapSets.Contains(currentCharacteristic)) { Song.difficultyBeatmapSets.Add(currentCharacteristic); } if (!currentCharacteristic.difficultyBeatmaps.Contains(diff)) { currentCharacteristic.difficultyBeatmaps.Add(diff); } BeatSaberMap map = TryGetExistingMapFromDiff(diff) ?? new BeatSaberMap { mainNode = new JSONObject() }; string oldPath = map?.directoryAndFile; diff.UpdateName(); map.directoryAndFile = Path.Combine(Song.directory, diff.beatmapFilename); if (File.Exists(oldPath) && oldPath != map.directoryAndFile && !File.Exists(map.directoryAndFile)) { if (firstSave) { File.Copy(oldPath, map.directoryAndFile); } else { File.Move(oldPath, map.directoryAndFile); //This should properly "convert" difficulties just fine } } else { map.Save(); } diff.RefreshRequirementsAndWarnings(map); Song.SaveSong(); characteristicSelect.Recalculate(); Debug.Log("Saved " + row.Name); }
/// <summary> /// Helper to deselect the currently selected row /// </summary> private void DeselectDiff() { if (selected != null) { var selImage = selected.Background; selImage.color = new Color(selImage.color.r, selImage.color.g, selImage.color.b, 0.0f); // Clean the UI, if we're selecting a new item they'll be repopulated BeatSaberSongContainer.Instance.difficultyData = null; njsField.text = ""; songBeatOffsetField.text = ""; envRemoval.ClearList(); } selected = null; }
/// <summary> /// Revert the diff to the saved version /// </summary> /// <param name="obj">UI row that was clicked on</param> private void Revertdiff(DifficultyRow row) { var localDiff = diffs[row.Name]; localDiff.Revert(); row.NameInput.text = localDiff.CustomName; if (row == selected) { njsField.text = localDiff.NoteJumpMovementSpeed.ToString(); songBeatOffsetField.text = localDiff.NoteJumpStartBeatOffset.ToString(); envRemoval.UpdateFromDiff(localDiff.envRemoval); } row.ShowDirtyObjects(localDiff); }
/// <summary> /// Set the row as the source for a copy-paste operation /// </summary> /// <param name="row">UI row that was clicked on</param> private void SetCopySource(DifficultyRow row) { // If we copied from the current characteristic remove the highlight if (copySource != null && currentCharacteristic == copySource.Characteristic) { copySource.Obj.CopyImage.color = Color.white; } // Clicking twice on the same source removes it if (copySource != null && copySource.Obj == row && currentCharacteristic == copySource.Characteristic) { CancelCopy(); return; } copySource = new CopySource(diffs[row.Name], currentCharacteristic, row); SetPasteMode(true); row.CopyImage.color = copyColor; }
/// <summary> /// Handle deleting a difficulty that was previously saved /// </summary> /// <param name="row">UI row that was clicked on</param> /// <param name="r">Confirmation from the user</param> private void HandleDeleteDifficulty(DifficultyRow row, int r) { if (r == 1) // User canceled out { row.Toggle.isOn = true; return; } var diff = diffs[row.Name].DifficultyBeatmap; string fileToDelete = Song.GetMapFromDifficultyBeatmap(diff)?.directoryAndFile; if (File.Exists(fileToDelete)) { FileOperationAPIWrapper.MoveToRecycleBin(fileToDelete); } // Remove status effects if present if (copySource != null && row == copySource.Obj && currentCharacteristic == copySource.Characteristic) { CancelCopy(); } if (row == selected) { DeselectDiff(); } currentCharacteristic.difficultyBeatmaps.Remove(diffs[row.Name].DifficultyBeatmap); if (currentCharacteristic.difficultyBeatmaps.Count == 0) { Song.difficultyBeatmapSets.Remove(currentCharacteristic); } diffs.Remove(row.Name); Song.SaveSong(); row.SetInteractable(false); row.NameInput.text = ""; row.ShowDirtyObjects(false, false); characteristicSelect.Recalculate(); }
/// <summary> /// Handle changes to the difficulty label /// </summary> /// <param name="row">UI row that was updated</param> /// <param name="difficultyLabel">New label value</param> private void OnValueChanged(DifficultyRow row, string difficultyLabel) { if (!diffs.ContainsKey(row.Name)) { return; } var diff = diffs[row.Name]; // Expert+ is special as the only difficulty that is different in JSON string defaultName = row.Name == "ExpertPlus" ? "Expert+" : row.Name; if (difficultyLabel != "" && difficultyLabel != defaultName) { diff.CustomName = difficultyLabel; } else { diff.CustomName = null; } row.ShowDirtyObjects(diff); }
public BeatmapDetails() { Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, Alpha = 0.5f, }, new FillFlowContainer <MetadataSegment>() { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Width = 0.4f, Direction = FillDirection.Vertical, LayoutDuration = 200, LayoutEasing = EasingTypes.OutQuint, Children = new[] { description = new MetadataSegment("Description"), source = new MetadataSegment("Source"), tags = new MetadataSegment("Tags") }, }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Width = 0.6f, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 15), Padding = new MarginPadding(10) { Top = 0 }, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, Alpha = 0.5f, }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), Padding = new MarginPadding(10), Children = new[] { circleSize = new DifficultyRow("Circle Size", 7), drainRate = new DifficultyRow("HP Drain"), overallDifficulty = new DifficultyRow("Accuracy"), approachRate = new DifficultyRow("Approach Rate"), stars = new DifficultyRow("Star Difficulty"), }, }, }, }, ratingsContainer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Alpha = 0, AlwaysPresent = true, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, Alpha = 0.5f, }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 25, Left = 15, Right = 15, }, Children = new Drawable[] { new OsuSpriteText { Text = "User Rating", Font = @"Exo2.0-Medium", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, ratingsBar = new Bar { RelativeSizeAxes = Axes.X, Height = 5, }, new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new[] { negativeRatings = new OsuSpriteText { Font = @"Exo2.0-Regular", Text = "0", }, positiveRatings = new OsuSpriteText { Font = @"Exo2.0-Regular", Text = "0", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, }, }, new OsuSpriteText { Text = "Rating Spread", TextSize = 14, Font = @"Exo2.0-Regular", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, ratingsGraph = new BarGraph { RelativeSizeAxes = Axes.X, Height = 50, }, }, }, }, }, retryFailContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Alpha = 0, Children = new Drawable[] { new OsuSpriteText { Text = "Points of Failure", Font = @"Exo2.0-Regular", }, new Container <BarGraph> { RelativeSizeAxes = Axes.X, Size = new Vector2(1 / 0.6f, 50), Children = new[] { retryGraph = new BarGraph { RelativeSizeAxes = Axes.Both, }, failGraph = new BarGraph { RelativeSizeAxes = Axes.Both, }, }, }, } }, }, }, loading = new LoadingAnimation() }; }
public CopySource(DifficultySettings difficultySettings, DifficultyBeatmapSet characteristic, DifficultyRow obj) { DifficultySettings = difficultySettings; Characteristic = characteristic; Obj = obj; }
/// <summary> /// Handle adding and deleting difficulties, they aren't added to the /// song being edited until they are saved so this method stages them /// </summary> /// <param name="row">UI row that was clicked on</param> /// <param name="val">True if the diff is being added</param> private void OnChange(DifficultyRow row, bool val) { if (!val && diffs.ContainsKey(row.Name)) // Delete if exists { // ForceDirty = has never been saved, don't ask for permission if (diffs[row.Name].ForceDirty) { if (row == selected) { DeselectDiff(); } diffs.Remove(row.Name); row.SetInteractable(false); row.NameInput.text = ""; row.ShowDirtyObjects(false, false); return; } // This diff has previously been saved, confirm deletion PersistentUI.Instance.ShowDialogBox("SongEditMenu", "deletediff.dialog", (r) => HandleDeleteDifficulty(row, r), PersistentUI.DialogBoxPresetType.YesNo, new object[] { diffs[row.Name].DifficultyBeatmap.difficulty }); } else if (val && !diffs.ContainsKey(row.Name)) // Create if does not exist { DifficultyBeatmap map = new DifficultyBeatmap(currentCharacteristic) { difficulty = row.Name, difficultyRank = diffRankLookup[row.Name] }; map.UpdateName(); if (copySource != null) { var fromDiff = copySource.DifficultySettings; CancelCopy(); if (fromDiff != null) { map.noteJumpMovementSpeed = fromDiff.DifficultyBeatmap.noteJumpMovementSpeed; map.noteJumpStartBeatOffset = fromDiff.DifficultyBeatmap.noteJumpStartBeatOffset; map.customData = fromDiff.DifficultyBeatmap.customData?.Clone(); // This sets the current filename as the filename for another diff and will trigger the copy on save map.UpdateName(fromDiff.DifficultyBeatmap.beatmapFilename); } } diffs[row.Name] = new DifficultySettings(map, true); row.ShowDirtyObjects(diffs[row.Name]); row.SetInteractable(true); OnClick(row); } else if (val) // Create, but already exists { // I don't know how this would happen anymore row.ShowDirtyObjects(diffs[row.Name]); row.SetInteractable(true); if (!loading) { OnClick(row); } } }
/// <summary> /// Paste from another difficulty to this one /// As the toggles are hidden in this mode and replaced with paste icons /// we just forward the click to the toggle below /// </summary> /// <param name="row">UI row that was clicked on</param> private void DoPaste(DifficultyRow row) { // This will trigger the code in OnChange below row.Toggle.isOn = true; }