OnCuiDraggableDrag
OnCuiDraggableDrag is called when a player moves a draggable CUI element that your plugin sent, giving you the element name and its new position each time the client reports a drag update.
void OnCuiDraggableDrag(BasePlayer player, string name, UnityEngine.Vector3 position, CommunityEntity.DraggablePositionSendType type)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer whose client reported the drag movement. Always check for null before using it. |
name | string | The name of the draggable CUI element (the name passed to the CuiDraggable component on the client), used to tell which draggable was moved. |
position | UnityEngine.Vector3 | The current position of the draggable as reported by the client, in the coordinate space the draggable uses (typically normalized anchor space within its parent). |
type | CommunityEntity.DraggablePositionSendType | A CommunityEntity.DraggablePositionSendType value describing whether this is the drag start, an in-progress update, or the release/ |
Return value
The hook fires as a notification and its return value is ignored, so returning null or any value has no effect on the drag. To change where the element ends up, send a new CUI update with your own CuiDraggable and position rather than returning from this hook.
How it works
The hook fires from CommunityEntity.Hook_DragRPC, which the game runs when a client sends a draggable position update for a CuiDraggable component. It fires whenever the player drags an element your plugin created: as the drag continues and when it is released, depending on what the client sends. The name identifies which draggable moved, so one handler can route updates for many panels. position is the client-reported position, useful for tracking, snapping back, or persisting layout. type distinguishes the drag lifecycle so you can ignore intermediate updates and only act on release. Since it fires from a client RPC, treat position as untrusted input: validate ranges before you store or act on values. Nothing about the hook is narrowed by parameter types here; declare the exact signature. The hook is optional and receives no return value, so it cannot block the drag.
Example plugin: Persist draggable panel positions per player
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Draggable Layout Saver", "RustPluginGenerator", "1.0.0")]
[Description("Stores per-player positions of a draggable CUI panel and reopens it where it was left.")]
public class DraggableLayoutSaver : RustPlugin
{
private const string PermUse = "draggablelayoutsaver.use";
private const string PanelName = "dls.panel";
private const string DragName = "dls.drag";
private PluginConfig config;
private Dictionary<string, Vector3> savedPositions;
private class PluginConfig
{
[JsonProperty("Default X anchor")]
public float DefaultX = 0.40f;
[JsonProperty("Default Y anchor")]
public float DefaultY = 0.40f;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Opened"] = "Panel opened. Drag the header to move it.",
["NoPermission"] = "You do not have permission to use this command.",
["DropHint"] = "Panel position saved: {0}, {1}"
}, this);
}
private string Msg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermUse, this);
savedPositions = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, Vector3>>(Name) ?? new Dictionary<string, Vector3>();
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player != null)
CuiHelper.DestroyUi(player, PanelName);
}
Interface.Oxide.DataFileSystem.WriteObject(Name, savedPositions);
}
[ChatCommand("layoutui")]
private void CmdLayout(BasePlayer player, string command, string[] args)
{
if (player == null || !player.IsConnected) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
Vector3 pos;
if (!savedPositions.TryGetValue(player.UserIDString, out pos))
pos = new Vector3(config.DefaultX, config.DefaultY, 0f);
ShowPanel(player, pos);
SendReply(player, Msg("Opened", player.UserIDString));
}
private void OnCuiDraggableDrag(CuiDraggableComponent draggable, BasePlayer player, string name, Vector3 position)
{
if (player == null || !player.IsConnected || player.IsNpc) return;
if (name != DragName) return;
float x = Mathf.Clamp(position.x, 0f, 1f);
float y = Mathf.Clamp(position.y, 0f, 1f);
savedPositions[player.UserIDString] = new Vector3(x, y, 0f);
SendReply(player, Msg("DropHint", player.UserIDString, x.ToString("0.00"), y.ToString("0.00")));
}
private void ShowPanel(BasePlayer player, Vector3 pos)
{
CuiHelper.DestroyUi(player, PanelName);
var container = new CuiElementContainer();
string anchorMin = string.Format("{0} {1}", pos.x, pos.y);
string anchorMax = string.Format("{0} {1}", pos.x + 0.25f, pos.y + 0.15f);
container.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.9" },
RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax },
CursorEnabled = true
}, "Overlay", PanelName);
container.Add(new CuiElement
{
Parent = PanelName,
Name = DragName,
Components =
{
new CuiRectTransformComponent { AnchorMin = "0 0.75", AnchorMax = "1 1" },
new CuiImageComponent { Color = "0.2 0.4 0.7 1" },
new CuiDraggableComponent { LimitToParent = true }
}
});
container.Add(new CuiLabel
{
Text = { Text = "Drag me", FontSize = 16, Align = TextAnchor.MiddleCenter },
RectTransform = { AnchorMin = "0 0.75", AnchorMax = "1 1" }
}, PanelName);
CuiHelper.AddUi(player, container);
}
}
}The plugin registers the chat command layoutui (permission draggablelayoutsaver.use) that opens a small CUI panel containing a draggable header element. OnCuiDraggableDrag filters on the draggable name dls.drag, clamps the reported normalized coordinates to 0..1 with Mathf.Clamp, and stores them in an in-memory dictionary keyed by the player's id. The dictionary is written to disk in Unload via Interface.Oxide.DataFileSystem, so positions survive restarts. Running layoutui again reopens the panel at the saved position (or the configured default X/Y from PluginConfig when none is stored). All player-facing text comes from the language file, and the command checks that the player is connected and has permission before showing the panel.
Common use cases
Save custom HUD layouts
Store where each player dragged their moved windows so the UI reopens in the same place next session.
Snap draggables to a grid
Read the reported position and resend the element at a rounded coordinate to enforce tidy alignment.
Track drag completion
Ignore intermediate updates and only persist or react when
typereports the release, cutting down on writes.
Pitfalls
Positions come from the client
A modified client can send arbitrary values. Clamp or reject out-of-range positions before storing them or feeding them back into CUI.
Name collisions
If two panels reuse the same draggable name, the handler cannot tell them apart. Prefix names per panel or per player.
Not every drag is a release
The hook fires on ongoing movement as well as release. Persisting on every call causes needless writes; check
typefirst.
Build a plugin with OnCuiDraggableDrag
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnCuiDraggableDrag
Generate