@Nonnull
	private HexOperations newHexOperations() {
		IntegerProperty focusedOffset = new SimpleIntegerProperty(0);
		BooleanProperty isHexColumActive = new SimpleBooleanProperty(true);
		HexAccess currentRead = () -> data;
		HexAccess originalRead = () -> dataDefaultState;
		HexNavigation navigation = new HexNavigation() {
			@Override
			public int selectionOffset() {
				return focusedOffset.get();
			}

			@Override
			public boolean isHexColumnSelected() {
				return isHexColumActive.get();
			}

			@Override
			public void switchColumns() {
				if (config.getShowAscii().getValue()) {
					// If we show both hex/ascii columns we want to toggle between the two and refresh the display.
					isHexColumActive.set(!isHexColumActive.get());
					refreshRowDisplay(focusedOffset.get());
				} else {
					// If we only show the hex column there is nothing to toggle between, and no reason to refresh.
					isHexColumActive.set(true);
				}
			}

			@Override
			public void select(int offset) {
				int max = currentRead.length();
				int clampedOffset = Math.clamp(offset, 0, max - 1);

				focusedOffset.setValue(clampedOffset);

				flow.visibleCells().forEach(c -> c.updateSelection(clampedOffset));

				bringOffsetIntoView(offset);
			}

			@Override
			public void selectNext() {
				select(focusedOffset.get() + 1);
			}

			@Override
			public void selectPrevious() {
				select(focusedOffset.get() - 1);
			}

			@Override
			public void selectDown() {
				select(focusedOffset.get() + config.getRowLength().getValue());
			}

			@Override
			public void selectUp() {
				select(focusedOffset.get() - config.getRowLength().getValue());
			}

			@Override
			public void selectDown(int rows) {
				select(focusedOffset.get() + config.getRowLength().getValue() * rows);
			}

			@Override
			public void selectUp(int rows) {
				select(focusedOffset.get() - config.getRowLength().getValue() * rows);
			}
		};
		return new HexOperations() {
			private final EventHandler<KeyEvent> listener = e -> {
				// If the focus is not in the editor, bring it in.
				if (!isFocusWithin()) flow.requestFocus();

				// Do not propagate the key-event up the scene graph.
				// Things like arrow keys and tabs if handled by parents can
				// unintentionally bring focus away from the editor component.
				e.consume();

				// Handle special keys.
				KeyCode code = e.getCode();
				HexNavigation nav = navigation();
				switch (code) {
					case ENTER -> engageCurrent();
					case ESCAPE -> cancelCurrent();
					case TAB -> nav.switchColumns();
					case PAGE_UP -> nav.selectUp(Math.max(1, flow.visibleCells().size() - 1));
					case PAGE_DOWN -> nav.selectDown(Math.max(1, flow.visibleCells().size() - 1));
					case RIGHT, KP_RIGHT -> nav.selectNext();
					case LEFT, KP_LEFT -> nav.selectPrevious();
					case UP, KP_UP -> nav.selectUp();
					case DOWN, KP_DOWN -> nav.selectDown();
				}
			};

			@Nonnull
			@Override
			public HexAccess currentAccess() {
				return currentRead;
			}

			@Nonnull
			@Override
			public HexAccess originalAccess() {
				return originalRead;
			}

			@Nonnull
			@Override
			public HexNavigation navigation() {
				return navigation;
			}

			@Override
			public void refreshDisplay(int offset, boolean asciiOrigin) {
				refreshRowDisplay(offset);
			}

			@Override
			public void engageCurrent() {
				engage((row, offset) -> row.engage(offset, true));

				// Engage can initiate editing in a cell, or commit work-in-progress edits.
				// When WIP edits are committed, the text editor is swapped out of the scene
				// which can cause a loss of focus. We'll catch that and re-focus here.
				if (!flow.isFocusWithin()) flow.requestFocus();
			}

			@Override
			public void cancelCurrent() {
				engage((row, offset) -> row.engage(offset, false));

				// Same reasoning as with engage-current above.
				if (!flow.isFocusWithin()) flow.requestFocus();
			}

			@Override
			public void sendKeyToCurrentEngaged(@Nonnull KeyCode code) {
				engage((row, offset) -> row.sendKeyToCurrentEngaged(offset, code));
			}

			@Nonnull
			@Override
			public EventHandler<KeyEvent> keyListener() {
				return listener;
			}

			private void engage(CellConsumer consumer) {
				int currentOffset = focusedOffset.get();

				bringOffsetIntoView(currentOffset);
				int rowLength = config.getRowLength().getValue();

				Optional<HexRow> cellIfVisible = flow.getCellIfVisible(offsetToRowIndex(currentOffset));
				if (cellIfVisible.isPresent()) {
					HexRow row = cellIfVisible.get();
					consumer.accept(row, currentOffset);
				}
			}

			interface CellConsumer {
				void accept(@Nonnull HexRow cell, int offset);
			}
		};
	}
@param offset
		Data offset.

@return Row index that contains the offset.
recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/hex/HexEditor.java