Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance RadialMenu Component Scalability and Text Handling #639

Merged
merged 5 commits into from
Sep 12, 2024

Conversation

enlistedmango
Copy link
Contributor

I'm picking up from a previous PR that I had started, but never finalised.
The inital PR can be found here


This is my first open source submission and I'd appreciate feedback on the changes. My hope here is that it'll improve the radial menu as a whole in terms of text, icons and being able to dynamically adjust the size based on the users needs.


Before

alt text

After

there are some visual changes made to this in order to better visualise each menu option

alt text

This change introduces two main enhancements to the RadialMenu component:

  1. Improved Text Wrapping: Implements conditional logic to better handle text wrapping within the SVG, ensuring that long labels do not overlap with other UI elements and are more readable.

  2. Dynamic Scaling: Adjusts the SVG dimensions to scale the entire component by an additional 5% to accommodate interface scalability requirements.

  3. Dynamic Font Size Adjustment: This function is designed to dynamically adjust the font size of the text labels in a given sector. In the case of the original comment of 'Fahrzeuginteraktionen' which means Garage in German. This dynamic adjustment, based on text length means that the text will remain readable.

Changes Detail

1. Text Splitting Utility: splitTextIntoLines

To address the challenge of text wrapping in SVG elements within React, which do not support automatic line breaking, I introduced a utility function named splitTextIntoLines. This function splits a single string of text into an array of strings, where each string represents a line that fits within a specified maximum character length. This approach ensures text does not overflow its designated area and remains readable.

Functionality:
The function takes two parameters:

  • text: The string of text to be split into lines.
  • maxCharPerLine: The maximum number of characters allowed per line.

It processes the text by splitting it at spaces to respect word boundaries and then reassembles lines until adding another word would exceed the maxCharPerLine limit. This method ensures that words are not abruptly cut off, maintaining readability and aesthetic appeal.

File Affected:

  • RadialMenu.tsx

Code Snippet:

const splitTextIntoLines = (text: string, maxCharPerLine: number): string[] => {
  const words = text.split(" ");
  const lines: string[] = [];
  let currentLine = words[0];

  for (let i = 1; i < words.length; i++) {
    if (currentLine.length + words[i].length + 1 <= maxCharPerLine) {
      currentLine += " " + words[i];
    } else {
      lines.push(currentLine);
      currentLine = words[i];
    }
  }
  lines.push(currentLine);
  return lines;
};

Rationale

  • Adaptability: By dynamically adjusting to the length of text content, the function helps prevent UI clutter and overlap, enhancing user interaction with the menu.

  • Scalability: This function allows text elements within the SVG to gracefully handle varying amounts of content, which is particularly important for applications with internationalization where text length can vary significantly.

2. Conditional Icon Spacing in Text Wrapping

  • Introduced a utility function calculateIconYOffset to adjust icon placement based on whether the associated text wraps to multiple lines.
  • Used a ternary operator to conditionally apply vertical spacing, improving the legibility and aesthetic of the menu items when text labels extend beyond a single line.

File Affected:

  • RadialMenu.tsx

Code Snippet:

const calculateIconYOffset = (label: string, maxCharPerLine: number): number => {
  const lines = splitTextIntoLines(label, maxCharPerLine);
  return lines.length > 1 ? 4 : 0; // Adjust icon position based on line count
};

...
<text
  y={iconY + calculateIconYOffset(item.label, 15)}
  ...
>
  {item.label}
</text>

3. SVG Scaling

  • Updated the SVG width and height attributes to scale the component size by a total of 110.25% relative to the original dimensions. This was achieved by compounding an additional 5% increase on the previously scaled size.

  • Adjusted the viewBox to maintain the original component's internal coordinate system, ensuring that the elements scale proportionally.

  • Recalculated the radial dimensions and positions within the SVG to maintain visual and functional integrity at the new size.

File Affected:

  • RadialMenu.tsx

Code Snippet:

const baseDimension = 350;
const scale = 1.1025; // compounded scale factor
const newDimension = baseDimension * scale;

return (
  <svg width={`${newDimension}px`} height={`${newDimension}px`} viewBox="0 0 350 350" transform="rotate(90)">
    ...
  </svg>
);

Rationale

  • Scalability: As the application's user interface is used in varied screen sizes and resolutions, scaling the RadialMenu ensures it remains functional and visually consistent across devices.

  • Readability: Handling text wrapping effectively prevents UI elements from overlapping, which is crucial in dense information displays like radial menus. The conditional positioning of icons ensures that the interface remains uncluttered and easy to interact with.

3. Dynamic Font Size Adjustment

The primary purpose of the calculateFontSize function is to maintain a balance between aesthetics and usability. As the length of the text labels grows, the available space for each label decreases. This function helps prevent text overlap and ensures all labels are appropriately scaled to fit within their designated sectors.

File Affected:

  • RadialMenu.tsx

Code Snippet:

const calculateFontSize = (text: string): number => {
  if (text.length > 20) return 10; // Smaller font size for very long texts
  if (text.length > 15) return 12; // Slightly smaller font size for long texts
  return 13; // Default font size for shorter texts
};

So as you can see by the code provided. The length can be adjusted based on the users needs.

Benefits

  • Improved Readability: By dynamically adjusting the font size, the function ensures that all text labels are legible, regardless of their length or the number of items in the menu.
  • Adaptive Design: The function allows the radial menu to remain visually appealing and functionally effective across a wide range of item counts and text lengths.
  • Prevents Overlap: Dynamic font size adjustments help maintain a clear, uncluttered appearance in the menu, preventing text overlap or cut-offs.

Conclusion

These changes aim to enhance the usability and visual quality of the Radial Menu within ox_lib.
I've not been using React for very long and I'd appreciate some feedback on these changes. My hope is that it'll provide easier use for those who don't want to tinker around and rebuild the app.

@LukeWasTakenn LukeWasTakenn self-assigned this Sep 9, 2024
Copy link
Member

@LukeWasTakenn LukeWasTakenn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall really good PR, though there's a lot of ChatGPT going on, especially with the comments so please remove those as these don't really add anything of value and get in the way.

I am not entirely sold on the stroke yet as it looks quite weird in-game, especially when there's only 2 items on a page.

image

web/src/features/menu/radial/index.tsx Outdated Show resolved Hide resolved
web/src/features/menu/radial/index.tsx Outdated Show resolved Hide resolved
web/src/features/menu/radial/index.tsx Outdated Show resolved Hide resolved
web/src/features/menu/radial/index.tsx Outdated Show resolved Hide resolved
@enlistedmango
Copy link
Contributor Author

enlistedmango commented Sep 10, 2024

Thanks for the comments Luke, I'm still learning and so this was very helpful to at least understand how these kinds of functionalities are built.

@enlistedmango
Copy link
Contributor Author

I understand what you mean about the stroke. Initially was this added because the community I was helping at the time wanted the seperation of sections. I agree it looks better when it's full of things.

Copy link
Member

@LukeWasTakenn LukeWasTakenn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I've pushed some misc style changes,

I don't mind the outline around the items but it would need to be implemented differently due to the flaws of just setting stroke as I've mentioned previously.

Thanks for the PR!

@LukeWasTakenn LukeWasTakenn merged commit 80267eb into overextended:master Sep 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants